elanous 0.2.34 → 0.2.36

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 (802) 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/chunks/{abb4b98b.163e0f56e84ae35d.js → 053a429e.9a63436527cc751c.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/109.f196abf3484bb97f.js +287 -0
  5. package/apps/pwa/out/_next/static/chunks/1201.afbfc5b1c5e5cbe4.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1517.9275b89572098ff3.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/1574.ad11f8db8396ab45.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/1614.7a8c5c40aee0261f.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/1871-f1eb29d8dbb4065b.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/190.285c832bd9626f8c.js +64 -0
  11. package/apps/pwa/out/_next/static/chunks/2193.0ea24c4a7aa1d004.js +36 -0
  12. package/apps/pwa/out/_next/static/chunks/223-ad865c1e80491d78.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/2265-f0d90b6dbec05f81.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/2267.eb4a62a209f09ec4.js +149 -0
  15. package/apps/pwa/out/_next/static/chunks/2341-86cd06c0cf01aa33.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/2352.9abc42422bade876.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/2397-c1653a816061842d.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/2500.cbc1257b774aae52.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/{6086.913a69e6fa3e4a8b.js → 2683.c9773a0c589b031b.js} +4 -4
  20. package/apps/pwa/out/_next/static/chunks/2709.8e1889034255fc5f.js +24 -0
  21. package/apps/pwa/out/_next/static/chunks/{5576-82d3703636d5c4b9.js → 275-02ea4c2a33980422.js} +3 -3
  22. package/apps/pwa/out/_next/static/chunks/{ffdc3a04.b9c23c1b28dd4a96.js → 2812ca6a.3b9f4a7a08885744.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/284.ac77c0db55bfd3a8.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/2941.3c3546f96bc6efa7.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/2976.b000698a3c6512dc.js +24 -0
  26. package/apps/pwa/out/_next/static/chunks/{9550.d5f76828f972058c.js → 2986.45d7652e4f13557e.js} +2 -2
  27. package/apps/pwa/out/_next/static/chunks/3031-85328a92fcce4da6.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/{8817.61bb7ffbba502ecb.js → 3136.30aaaa5fa03662a3.js} +1 -1
  29. package/apps/pwa/out/_next/static/chunks/{9131-fce7256c4180c4a5.js → 3148-98efd4869d660a87.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/{3498.cf2a15a8a9e5b926.js → 3155.2587a55196ecb394.js} +3 -3
  31. package/apps/pwa/out/_next/static/chunks/3277.785db1cf09f98601.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/3320-f9c77381f85a2c0a.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/{3103.84470c8445962f04.js → 3324.a09703033abcbd2f.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/3386-91049feabff9a94e.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/339.80afb7e8839372af.js +8 -0
  36. package/apps/pwa/out/_next/static/chunks/3410.7f9af5ab77cbe65f.js +115 -0
  37. package/apps/pwa/out/_next/static/chunks/{3819.163d4777f0a597f6.js → 3538.a445e50bb359d068.js} +2 -2
  38. package/apps/pwa/out/_next/static/chunks/3540c645.2595a6af01a97672.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/3621.581b2054cc14bee4.js +286 -0
  40. package/apps/pwa/out/_next/static/chunks/3676.8c28d16fd84ca843.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/{3414.172ad4ca2d9b08e8.js → 3727.7949ccaacaa71242.js} +2 -2
  42. package/apps/pwa/out/_next/static/chunks/375-b84540853523fe52.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/3757-180308bc61021b05.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/379.ee262e37586fe1ae.js +74 -0
  45. package/apps/pwa/out/_next/static/chunks/3840-5825d56d46976a92.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/3969-81054381eaec4c4c.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/{9b48c871.204d5d13e7809bd0.js → 3a3d9966.cc4d4585a2708e7c.js} +1 -1
  48. package/apps/pwa/out/_next/static/chunks/4067-bf9aa60547972937.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/4116.a84a92a2492c080e.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/4176.e5729c93c29151eb.js +104 -0
  51. package/apps/pwa/out/_next/static/chunks/4227-e6c84686bc8946ca.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/{6740.43de73fc427c9fe2.js → 4286.b70c4520306817e8.js} +1 -1
  53. package/apps/pwa/out/_next/static/chunks/4304.e9b2512834a9e23e.js +23 -0
  54. package/apps/pwa/out/_next/static/chunks/4327.b9e08a71c0009348.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/{8548.036c344973968847.js → 4615.8525eb0add35eba6.js} +1 -1
  56. package/apps/pwa/out/_next/static/chunks/4946.bd106eafd668f248.js +148 -0
  57. package/apps/pwa/out/_next/static/chunks/{3411-f87314c6c66ebc73.js → 5085-bce4904931767ca6.js} +1 -1
  58. package/apps/pwa/out/_next/static/chunks/5125.2eb9968b83767193.js +93 -0
  59. package/apps/pwa/out/_next/static/chunks/5128-75b733fd74a9a1eb.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/{3110.0a7065f25c841818.js → 528.38ce20ef8aa251ff.js} +16 -16
  61. package/apps/pwa/out/_next/static/chunks/5489f3fe.4bb1458b09f6dead.js +122 -0
  62. package/apps/pwa/out/_next/static/chunks/{1644.35dd25240ae24e4b.js → 552.b7d061ad91eeac51.js} +2 -2
  63. package/apps/pwa/out/_next/static/chunks/5560-2a4648f6a30f16e7.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/{3837.722e053fb9f321ad.js → 5711.957b095705c12560.js} +1 -1
  65. package/apps/pwa/out/_next/static/chunks/{2124.ed5bf480cea7f85b.js → 575.d929793a53f27c4b.js} +1 -1
  66. package/apps/pwa/out/_next/static/chunks/5772-e29e14f8ff127122.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/5887.5b2f5b44ce0f7e9a.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/5899-4c112a2d908cf97f.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/{7562.948255cbc3e79d33.js → 599.c39c571b6733f2a2.js} +2 -2
  70. package/apps/pwa/out/_next/static/chunks/6041.1b65efb796ebc2a5.js +79 -0
  71. package/apps/pwa/out/_next/static/chunks/6057-765c0a26028c86b2.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/6142.f289e0d039f453a6.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/6199-6bcd61d2a3910e04.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/6236.cdf7293e710a9b2d.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/6387-237e079dac9a3ae6.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/6448-b6d272d074276e14.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/6466-92e65a48490b68a9.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/6566.926a1d1b7c911442.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/6580-cdc0e4a634ce4708.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/6659-b242dc31b20b3f29.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/6690.c5d9b321b1f79384.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/671-e9b6ad0c771c5514.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/6862.bcbf89dd0f4496b3.js +27 -0
  84. package/apps/pwa/out/_next/static/chunks/6912-7b375ee48732c767.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/{5446.edf141cff9ef647d.js → 6925.562d532110879886.js} +1 -1
  86. package/apps/pwa/out/_next/static/chunks/{7005.1623d2aea78eb85f.js → 6943.c6b24124853a47b2.js} +1 -1
  87. package/apps/pwa/out/_next/static/chunks/727.7deb87749b0a343a.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/{8368.014caa8de2f5af34.js → 7481.39f7a4654907bf6e.js} +1 -1
  89. package/apps/pwa/out/_next/static/chunks/7545.b1aa42cd15503319.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/7551-416d298c2dc4a9ab.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/{2554.3b5e200f5f5cfcd6.js → 771.c3cdb6c1f7f47c02.js} +16 -16
  92. package/apps/pwa/out/_next/static/chunks/7784.1f0ac4a16d190cf4.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/7870-309de463786ac1fc.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/7888-e29b151bb6a3bb8b.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/{3121.779fafe3e44612c5.js → 7891.03f910124a737985.js} +1 -1
  96. package/apps/pwa/out/_next/static/chunks/7905-6ba78d9234608f79.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/8037.e254fd797aa224d1.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/8207.f527886b7c464529.js +1 -0
  99. package/apps/pwa/out/_next/static/chunks/856.50bda61439d465e6.js +62 -0
  100. package/apps/pwa/out/_next/static/chunks/{9a8cb466.cc9fc9e6e46038aa.js → 873d6c12.09bd499f57171543.js} +1 -1
  101. package/apps/pwa/out/_next/static/chunks/{7431.3051e9b8a4372d2d.js → 8904.7d51af2dc033965c.js} +2 -2
  102. package/apps/pwa/out/_next/static/chunks/9031-92e9b487edb681ee.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/9060.5410d9f6a95f6287.js +90 -0
  104. package/apps/pwa/out/_next/static/chunks/914.8abefd48d579547b.js +94 -0
  105. package/apps/pwa/out/_next/static/chunks/9187-c87a2207d4ac5d46.js +1 -0
  106. package/apps/pwa/out/_next/static/chunks/{1895-60ed0e341c658bd4.js → 9250-072df22eaadaa6b5.js} +1 -1
  107. package/apps/pwa/out/_next/static/chunks/9289.3e92f278821e18e0.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/9319.7816d3cc741cd34f.js +1 -0
  109. package/apps/pwa/out/_next/static/chunks/9327-e016bb9f1674d24f.js +1 -0
  110. package/apps/pwa/out/_next/static/chunks/9365.3de48bf75193ff9a.js +77 -0
  111. package/apps/pwa/out/_next/static/chunks/9383.5b5e9aa56b5fea97.js +1 -0
  112. package/apps/pwa/out/_next/static/chunks/9387-cb929573ef436f39.js +1 -0
  113. package/apps/pwa/out/_next/static/chunks/9489.c27becacc3283dec.js +1 -0
  114. package/apps/pwa/out/_next/static/chunks/{2547.dd27303624cb4da8.js → 9514.131c52cc75fe6439.js} +1 -1
  115. package/apps/pwa/out/_next/static/chunks/9536.28962663c7734376.js +1 -0
  116. package/apps/pwa/out/_next/static/chunks/9686-12286f5bbf15a5a0.js +1 -0
  117. package/apps/pwa/out/_next/static/chunks/9776.289d40cc5a4c342a.js +1 -0
  118. package/apps/pwa/out/_next/static/chunks/9789.60f723d18097ec30.js +1 -0
  119. package/apps/pwa/out/_next/static/chunks/989.f0c17523c5e61f65.js +1 -0
  120. package/apps/pwa/out/_next/static/chunks/9972-1df393aa8d4f7d52.js +1 -0
  121. package/apps/pwa/out/_next/static/chunks/9f31e478.fefcbcd3c388ebc5.js +1 -0
  122. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-96750aba7430959f.js +1 -0
  123. package/apps/pwa/out/_next/static/chunks/app/approvals/page-4105e5bbbb043024.js +1 -0
  124. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5d4fa75c2acb113e.js +1 -0
  125. package/apps/pwa/out/_next/static/chunks/app/board/page-a79a9bdb3b2142bf.js +1 -0
  126. package/apps/pwa/out/_next/static/chunks/app/botlab/page-f1c2047484992a35.js +1 -0
  127. package/apps/pwa/out/_next/static/chunks/app/bots/page-15ffc49b2d8c70e2.js +1 -0
  128. package/apps/pwa/out/_next/static/chunks/app/ceo/page-67d53a3fc8734135.js +1 -0
  129. package/apps/pwa/out/_next/static/chunks/app/chat/page-e30c8dc696491336.js +1 -0
  130. package/apps/pwa/out/_next/static/chunks/app/consult/page-2136e04fa77696e2.js +1 -0
  131. package/apps/pwa/out/_next/static/chunks/app/control/page-fecd2814790c2e12.js +1 -0
  132. package/apps/pwa/out/_next/static/chunks/app/decisions/page-280aa4a98fc5bb44.js +1 -0
  133. package/apps/pwa/out/_next/static/chunks/app/design-check/page-c14e96b65fb4b883.js +1 -0
  134. package/apps/pwa/out/_next/static/chunks/app/editor/page-187dac74825ed6bf.js +1 -0
  135. package/apps/pwa/out/_next/static/chunks/app/exec/page-6bc7246ea8c92448.js +1 -0
  136. package/apps/pwa/out/_next/static/chunks/app/field/{page-4fb31eb476dcbad3.js → page-083747f1d4518f61.js} +1 -1
  137. package/apps/pwa/out/_next/static/chunks/app/healthz/route-96750aba7430959f.js +1 -0
  138. package/apps/pwa/out/_next/static/chunks/app/inside/page-15ca95a16ec96e1f.js +1 -0
  139. package/apps/pwa/out/_next/static/chunks/app/intake/page-a8ac031b3810eb64.js +1 -0
  140. package/apps/pwa/out/_next/static/chunks/app/layout-8835f93110494cda.js +1 -0
  141. package/apps/pwa/out/_next/static/chunks/app/live/page-e2de2e59111c9aad.js +1 -0
  142. package/apps/pwa/out/_next/static/chunks/app/live-run/page-f2b2e4bf6e981450.js +1 -0
  143. package/apps/pwa/out/_next/static/chunks/app/loops/page-5b92b7f75df20948.js +1 -0
  144. package/apps/pwa/out/_next/static/chunks/app/market/page-468eddf2b1a84ecb.js +1 -0
  145. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-2af16e62c12f22df.js +1 -0
  146. package/apps/pwa/out/_next/static/chunks/app/morning/page-18b40867a77a5793.js +1 -0
  147. package/apps/pwa/out/_next/static/chunks/app/not-found-fe934f2979f5a4a2.js +1 -0
  148. package/apps/pwa/out/_next/static/chunks/app/observatory/page-3eec435537357170.js +1 -0
  149. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ff88cbfd2aa99fc8.js +1 -0
  150. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a5b39fb780c70ab5.js +1 -0
  151. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-ae465dde1d1b3933.js +1 -0
  152. package/apps/pwa/out/_next/static/chunks/app/outputs/page-e7abc34a48a6f71e.js +1 -0
  153. package/apps/pwa/out/_next/static/chunks/app/page-cab9674fde9ead26.js +1 -0
  154. package/apps/pwa/out/_next/static/chunks/app/reflection/page-1da49bc8712284d0.js +1 -0
  155. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-5e8aa657551ac912.js +1 -0
  156. package/apps/pwa/out/_next/static/chunks/app/sessions/page-fcc36f36c6dc1fe2.js +1 -0
  157. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-d22db22f2a7296ca.js +1 -0
  158. package/apps/pwa/out/_next/static/chunks/app/settings/page-d12dc7b1773878f0.js +1 -0
  159. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-99debec9ea7fbec0.js +1 -0
  160. package/apps/pwa/out/_next/static/chunks/app/setup/layout-96750aba7430959f.js +1 -0
  161. package/apps/pwa/out/_next/static/chunks/app/setup/page-2a088a78bad7e924.js +1 -0
  162. package/apps/pwa/out/_next/static/chunks/app/share/page-2e8ed57db7e69993.js +1 -0
  163. package/apps/pwa/out/_next/static/chunks/app/showroom/page-4b3a16213c2ae8fe.js +1 -0
  164. package/apps/pwa/out/_next/static/chunks/app/tasks/page-f098747ddf38c98b.js +1 -0
  165. package/apps/pwa/out/_next/static/chunks/app/term/page-c15958835534404b.js +1 -0
  166. package/apps/pwa/out/_next/static/chunks/app/today/page-68279e3c843c1fd7.js +1 -0
  167. package/apps/pwa/out/_next/static/chunks/app/trace/page-a34a74453367a39d.js +1 -0
  168. package/apps/pwa/out/_next/static/chunks/app/vault/page-8767a7db2155a5bd.js +1 -0
  169. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-bd5976683460d7f2.js +1 -0
  170. package/apps/pwa/out/_next/static/chunks/app/workflows/page-b7364835e7f2ef54.js +1 -0
  171. package/apps/pwa/out/_next/static/chunks/app/workspace/page-311af16b3ee0ffca.js +1 -0
  172. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-fb670757274ae990.js +1 -0
  173. package/apps/pwa/out/_next/static/chunks/{5014f970-22bef0b5210f7d60.js → ccae1c4b-0cf414f5eff1b10d.js} +1 -1
  174. package/apps/pwa/out/_next/static/chunks/{4fa33ebd.21c1bd5b294496e6.js → ceba0382.650712b56b85b582.js} +1 -1
  175. package/apps/pwa/out/_next/static/chunks/da5ecc5c-b005ad8cac737d57.js +1 -0
  176. package/apps/pwa/out/_next/static/chunks/f9a61e4d.1fc2c1dbdd49fe46.js +1 -0
  177. package/apps/pwa/out/_next/static/chunks/framework-6d497cbf1eef04c6.js +1 -0
  178. package/apps/pwa/out/_next/static/chunks/main-app-012f5edec932f337.js +1 -0
  179. package/apps/pwa/out/_next/static/chunks/main-da5aeb809edc94a2.js +1 -0
  180. package/apps/pwa/out/_next/static/chunks/pages/_app-5ee319af6f749dfe.js +1 -0
  181. package/apps/pwa/out/_next/static/chunks/pages/_error-4bc84653a718d6c6.js +1 -0
  182. package/apps/pwa/out/_next/static/chunks/webpack-8a96cf6775c73d6e.js +1 -0
  183. package/apps/pwa/out/_next/static/{L04y73dlwKeTW-GetaTF3 → uC-N0emedeNOFuaBQFv-I}/_buildManifest.js +1 -1
  184. package/apps/pwa/out/approvals/index.html +1 -1
  185. package/apps/pwa/out/approvals/index.txt +22 -22
  186. package/apps/pwa/out/autopilot/index.html +1 -1
  187. package/apps/pwa/out/autopilot/index.txt +22 -22
  188. package/apps/pwa/out/board/index.html +1 -1
  189. package/apps/pwa/out/board/index.txt +22 -22
  190. package/apps/pwa/out/botlab/index.html +1 -1
  191. package/apps/pwa/out/botlab/index.txt +21 -21
  192. package/apps/pwa/out/bots/index.html +1 -1
  193. package/apps/pwa/out/bots/index.txt +21 -21
  194. package/apps/pwa/out/ceo/index.html +1 -1
  195. package/apps/pwa/out/ceo/index.txt +22 -22
  196. package/apps/pwa/out/chat/index.html +1 -1
  197. package/apps/pwa/out/chat/index.txt +21 -21
  198. package/apps/pwa/out/consult/index.html +1 -1
  199. package/apps/pwa/out/consult/index.txt +22 -22
  200. package/apps/pwa/out/control/index.html +1 -1
  201. package/apps/pwa/out/control/index.txt +22 -22
  202. package/apps/pwa/out/decisions/index.html +1 -1
  203. package/apps/pwa/out/decisions/index.txt +21 -21
  204. package/apps/pwa/out/design-check/index.html +1 -1
  205. package/apps/pwa/out/design-check/index.txt +22 -22
  206. package/apps/pwa/out/editor/index.html +1 -1
  207. package/apps/pwa/out/editor/index.txt +22 -22
  208. package/apps/pwa/out/exec/index.html +1 -1
  209. package/apps/pwa/out/exec/index.txt +22 -22
  210. package/apps/pwa/out/field/index.html +1 -1
  211. package/apps/pwa/out/field/index.txt +22 -22
  212. package/apps/pwa/out/healthz +1 -1
  213. package/apps/pwa/out/index.html +1 -1
  214. package/apps/pwa/out/index.txt +21 -21
  215. package/apps/pwa/out/inside/index.html +1 -1
  216. package/apps/pwa/out/inside/index.txt +21 -21
  217. package/apps/pwa/out/intake/index.html +1 -1
  218. package/apps/pwa/out/intake/index.txt +22 -22
  219. package/apps/pwa/out/live/index.html +1 -1
  220. package/apps/pwa/out/live/index.txt +22 -22
  221. package/apps/pwa/out/live-run/index.html +1 -1
  222. package/apps/pwa/out/live-run/index.txt +22 -22
  223. package/apps/pwa/out/loops/index.html +1 -1
  224. package/apps/pwa/out/loops/index.txt +21 -21
  225. package/apps/pwa/out/market/index.html +1 -1
  226. package/apps/pwa/out/market/index.txt +21 -21
  227. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  228. package/apps/pwa/out/missions/placeholder/index.txt +21 -21
  229. package/apps/pwa/out/morning/index.html +1 -1
  230. package/apps/pwa/out/morning/index.txt +22 -22
  231. package/apps/pwa/out/observatory/index.html +1 -1
  232. package/apps/pwa/out/observatory/index.txt +22 -22
  233. package/apps/pwa/out/ops/checklist/index.html +1 -1
  234. package/apps/pwa/out/ops/checklist/index.txt +21 -21
  235. package/apps/pwa/out/ops/release/index.html +1 -1
  236. package/apps/pwa/out/ops/release/index.txt +21 -21
  237. package/apps/pwa/out/ops/seats/index.html +1 -1
  238. package/apps/pwa/out/ops/seats/index.txt +21 -21
  239. package/apps/pwa/out/outputs/index.html +1 -1
  240. package/apps/pwa/out/outputs/index.txt +21 -21
  241. package/apps/pwa/out/reflection/index.html +1 -1
  242. package/apps/pwa/out/reflection/index.txt +22 -22
  243. package/apps/pwa/out/scheduler/index.html +1 -1
  244. package/apps/pwa/out/scheduler/index.txt +22 -22
  245. package/apps/pwa/out/sessions/index.html +1 -1
  246. package/apps/pwa/out/sessions/index.txt +21 -21
  247. package/apps/pwa/out/settings/devices/index.html +1 -1
  248. package/apps/pwa/out/settings/devices/index.txt +22 -22
  249. package/apps/pwa/out/settings/index.html +1 -1
  250. package/apps/pwa/out/settings/index.txt +22 -22
  251. package/apps/pwa/out/setup/done/index.html +1 -1
  252. package/apps/pwa/out/setup/done/index.txt +22 -22
  253. package/apps/pwa/out/setup/index.html +1 -1
  254. package/apps/pwa/out/setup/index.txt +22 -22
  255. package/apps/pwa/out/share/index.html +1 -1
  256. package/apps/pwa/out/share/index.txt +22 -22
  257. package/apps/pwa/out/showroom/index.html +1 -1
  258. package/apps/pwa/out/showroom/index.txt +21 -21
  259. package/apps/pwa/out/tasks/index.html +1 -1
  260. package/apps/pwa/out/tasks/index.txt +22 -22
  261. package/apps/pwa/out/term/index.html +1 -1
  262. package/apps/pwa/out/term/index.txt +22 -22
  263. package/apps/pwa/out/today/index.html +1 -1
  264. package/apps/pwa/out/today/index.txt +22 -22
  265. package/apps/pwa/out/trace/index.html +1 -1
  266. package/apps/pwa/out/trace/index.txt +22 -22
  267. package/apps/pwa/out/vault/index.html +1 -1
  268. package/apps/pwa/out/vault/index.txt +22 -22
  269. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  270. package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
  271. package/apps/pwa/out/workflows/index.html +1 -1
  272. package/apps/pwa/out/workflows/index.txt +22 -22
  273. package/apps/pwa/out/workspace/index.html +1 -1
  274. package/apps/pwa/out/workspace/index.txt +22 -22
  275. package/apps/pwa/out/worktrees/index.html +1 -1
  276. package/apps/pwa/out/worktrees/index.txt +22 -22
  277. package/bin/elanous.mjs +3 -2
  278. package/catalog/providers/opengateway.yaml +25 -0
  279. package/docs/goal-context/01-goal-authoring-checks.md +8 -0
  280. package/docs/goal-context/03-verification-menu.md +24 -0
  281. package/docs/goal-context/04-mock-scenarios.md +100 -0
  282. package/docs/goal-context/05-authoring-lessons.jsonl +47 -0
  283. package/docs/goal-context/README.md +7 -0
  284. package/graphs/catalog/catalog.yaml +1 -1
  285. package/graphs/default-loop.yaml +63 -4
  286. package/graphs/document-loop.yaml +51 -3
  287. package/graphs/implement-loop.yaml +77 -4
  288. package/graphs/journey/ta-journey.yaml +11 -1
  289. package/graphs/journey/units/author-dialogue/author-dialogue.yaml +42 -0
  290. package/graphs/journey/units/author-dialogue/observe.ts +96 -0
  291. package/graphs/journey/units/author-dialogue/recipes.yaml +10 -0
  292. package/graphs/operate-loop.yaml +51 -3
  293. package/graphs/release/recipes.yaml +6 -0
  294. package/graphs/release/release-loop.yaml +17 -4
  295. package/graphs/research-loop.yaml +137 -3
  296. package/graphs/test-diet/test-diet.yaml +3 -0
  297. package/graphs/wizard/recipes.yaml +44 -0
  298. package/graphs/wizard/wizard-loop.yaml +44 -0
  299. package/graphs/work/research-report.yaml +2 -0
  300. package/package.json +1 -1
  301. package/scripts/ci-typecheck-changed.ts +139 -5
  302. package/scripts/coord-role-pack.ts +19 -6
  303. package/scripts/first10-gate.ts +416 -0
  304. package/scripts/fleet/wsl-node-setup.sh +432 -0
  305. package/scripts/hq/standby-snapshot.ts +17 -12
  306. package/scripts/release-loop/docs-land-node.ts +21 -5
  307. package/scripts/release-loop/docs-node.ts +204 -8
  308. package/scripts/release-loop/gate-baseline-receipt.ts +155 -0
  309. package/scripts/release-loop/gate-node.ts +427 -72
  310. package/scripts/release-loop/gate-partial.ts +65 -11
  311. package/scripts/release-loop/gate-radius-live.ts +177 -0
  312. package/scripts/release-loop/gate-radius-shadow.ts +1 -1
  313. package/scripts/release-loop/node-verdict.ts +14 -1
  314. package/scripts/release-loop/npm-publish-node.ts +66 -12
  315. package/scripts/release-loop/probe-pod-host-dirs-live.ts +5 -0
  316. package/scripts/release-loop/pwa-remote.ts +166 -0
  317. package/scripts/release-loop/pwa-scenarios-node.ts +19 -3
  318. package/scripts/release-loop/resume-cap.ts +14 -34
  319. package/scripts/release-loop/resume-release.ts +78 -15
  320. package/scripts/release-loop/traffic-node.ts +59 -0
  321. package/scripts/release-loop/traffic-transition.ts +159 -0
  322. package/scripts/release-loop/unattended-release.ts +2 -0
  323. package/scripts/rhythm/daily.ts +137 -3
  324. package/scripts/test-diet/lib.ts +6 -1
  325. package/scripts/test-diet/node.ts +194 -13
  326. package/scripts/ux-sim.ts +92 -0
  327. package/skills/README.md +1 -0
  328. package/src/acp/core-turn-bridge.ts +48 -6
  329. package/src/agent/delegate-hint.ts +16 -0
  330. package/src/agent/runner.ts +82 -2
  331. package/src/agent/self-ambient-cache.ts +8 -0
  332. package/src/agent/task-notification.ts +56 -10
  333. package/src/agent/types.ts +4 -0
  334. package/src/agent-substrate/acp-reviewer.ts +39 -4
  335. package/src/agent-substrate/pr-reviewer.ts +72 -3
  336. package/src/agent-substrate/review-intent.ts +29 -2
  337. package/src/agent-substrate/review-observation.ts +3 -0
  338. package/src/agent-substrate/self-review-cli.ts +229 -28
  339. package/src/autopilot/mission-codebase-gate.ts +141 -12
  340. package/src/autopilot/pr-manager.ts +240 -41
  341. package/src/boot/daemon-tools/self-implement.ts +1 -0
  342. package/src/budget/run-usage-rollup.ts +1 -0
  343. package/src/budget/usage-outcomes.ts +8 -4
  344. package/src/capture/tui-self-report.ts +102 -0
  345. package/src/chat/goal-continue-verdict.ts +41 -0
  346. package/src/chat/index.ts +30 -6
  347. package/src/chat/internal-turn-origin.ts +1 -0
  348. package/src/chat/paste-fold.ts +86 -0
  349. package/src/chat/pickers/modal-runtime.ts +10 -27
  350. package/src/chat/tool-render/block.ts +32 -0
  351. package/src/chat/tool-render/dispatch.ts +2 -20
  352. package/src/chat/tool-render/result-text.ts +98 -0
  353. package/src/chat/turn-typeahead.ts +70 -7
  354. package/src/cli/agent-cli.ts +13 -2
  355. package/src/cli/config-audit.ts +119 -0
  356. package/src/cli/doctor-cli.ts +26 -1
  357. package/src/cli/guardian-cli.ts +10 -0
  358. package/src/cli/herdr-cli.ts +34 -0
  359. package/src/cli/light-entry-route.ts +50 -7
  360. package/src/cli/log-event-names.ts +2 -0
  361. package/src/cli/make-cli.ts +247 -11
  362. package/src/cli/msg-cli.ts +117 -3
  363. package/src/cli/pod-cli.ts +77 -3
  364. package/src/cli/pr-cli.ts +34 -18
  365. package/src/cli/pr-land-base-read.ts +11 -2
  366. package/src/cli/pr-land-heavy-remote.ts +6 -3
  367. package/src/cli/pr-land-overlap-evidence.ts +23 -4
  368. package/src/cli/pr-land-pod-shadow-job.ts +3 -0
  369. package/src/cli/pr-land-recovery.ts +117 -1
  370. package/src/cli/pty-takeover-cli.ts +8 -7
  371. package/src/cli/release-checklist-read-cli.ts +79 -0
  372. package/src/cli/release-cli.ts +92 -106
  373. package/src/cli/release-single-flight.ts +1 -1
  374. package/src/cli/remote-resolve.ts +1 -1
  375. package/src/cli/root-tui-entry.ts +48 -0
  376. package/src/cli/sim-cli.ts +22 -0
  377. package/src/cli/tasks-card-show.ts +162 -0
  378. package/src/cli/tasks-cli.ts +1065 -338
  379. package/src/cli/tasks-read-cli.ts +197 -0
  380. package/src/cli/usage-cli.ts +13 -11
  381. package/src/clipboard/history.ts +179 -13
  382. package/src/config.ts +8 -1
  383. package/src/core-turn/run-core-turn.ts +4 -2
  384. package/src/core-turn/run-goal-loop.ts +73 -16
  385. package/src/core-turn/types.ts +6 -1
  386. package/src/dashboard/chat/acp-chat.ts +8 -0
  387. package/src/dashboard/context/keys.ts +8 -31
  388. package/src/dashboard/index.ts +280 -59
  389. package/src/dashboard/input/agent-wake.ts +7 -1
  390. package/src/dashboard/input/chat-main-plain-turn-runtime.ts +146 -3
  391. package/src/dashboard/input/chat-main-slash-command.ts +4 -2
  392. package/src/dashboard/input/goal-continue.ts +210 -0
  393. package/src/dashboard/input/goal-interim.ts +2 -0
  394. package/src/dashboard/input/internal-turn.ts +2 -8
  395. package/src/dashboard/input/seat-autowake.ts +9 -7
  396. package/src/dashboard/input/turn-autoheal.ts +52 -0
  397. package/src/dashboard/render/theme-resolver.ts +33 -4
  398. package/src/dashboard/seat-boot-preamble.ts +132 -5
  399. package/src/dashboard/slash-runtime/chat-persona.ts +65 -8
  400. package/src/dashboard/slash-runtime/dashboard-handlers.ts +3 -2
  401. package/src/dashboard/slash-runtime/model-choices.ts +15 -0
  402. package/src/dashboard/stale-install-check.ts +3 -99
  403. package/src/debug/batch-queue.ts +119 -0
  404. package/src/debug/tui-log-tag.ts +67 -0
  405. package/src/display/agent-surface.ts +7 -0
  406. package/src/domains/schedule-registry.ts +16 -2
  407. package/src/domains/standalone-log-sink.ts +15 -2
  408. package/src/esc-abort-gate.ts +69 -0
  409. package/src/git-fs/idle-refresh.ts +76 -0
  410. package/src/git-fs/index.ts +18 -7
  411. package/src/git-fs/worktree.ts +137 -5
  412. package/src/graph-kinds/registry.ts +2 -2
  413. package/src/graph-runner/graph-cli.ts +18 -0
  414. package/src/graph-runner/runner.ts +173 -21
  415. package/src/graph-wizard/generate.ts +108 -4
  416. package/src/graph-wizard/steps.ts +15 -0
  417. package/src/grok/credential.ts +10 -3
  418. package/src/grounding/proactive-ground.ts +86 -0
  419. package/src/harness/control-inbox.ts +49 -1
  420. package/src/harness/harness-cli-command.ts +163 -35
  421. package/src/harness/harness-incidents.ts +85 -1
  422. package/src/harness/launch-utterance-ledger.ts +208 -0
  423. package/src/harness/pod-control-send.ts +33 -5
  424. package/src/harness/self-send-remote.ts +69 -0
  425. package/src/harness/self-send-routing-guard.ts +6 -0
  426. package/src/harness/self-send-target.ts +4 -3
  427. package/src/impl-discipline/detector.ts +16 -4
  428. package/src/impl-discipline/system-prompt.ts +11 -0
  429. package/src/impl-discipline/types.ts +2 -2
  430. package/src/index.ts +822 -154
  431. package/src/input-core/context-key-runtime.ts +21 -0
  432. package/src/intake-plane/already-built-hint.ts +4 -2
  433. package/src/intake-plane/requirement-funnel.ts +29 -1
  434. package/src/light-cli.ts +32 -11
  435. package/src/llm/opengateway-models.ts +11 -0
  436. package/src/llm/provider-credentials.ts +14 -0
  437. package/src/llm/provider-detect.ts +8 -1
  438. package/src/llm/runtime-model-family.ts +2 -1
  439. package/src/llm-wellformed.ts +70 -0
  440. package/src/llm.ts +247 -59
  441. package/src/loops/msg-wake.ts +35 -39
  442. package/src/loops/orchestrator/inbox.ts +59 -11
  443. package/src/loops/orchestrator/knowhow-pack.ts +364 -0
  444. package/src/loops/orchestrator/tick.ts +5 -1
  445. package/src/maturity/feature-maturity.ts +1 -1
  446. package/src/model-tier/llm-tier-map.ts +15 -1
  447. package/src/models/prompts.ts +17 -2
  448. package/src/msg/msg-post-office.ts +116 -0
  449. package/src/mss/logging/log-store.ts +79 -22
  450. package/src/native-tool-catalog.ts +15 -1
  451. package/src/nexus/api/decisions-route.ts +38 -18
  452. package/src/nexus/api/graph-wizard-api.ts +20 -2
  453. package/src/nexus/api/loop-resources.ts +13 -1
  454. package/src/nexus/boot/register-mcp-clients.ts +263 -38
  455. package/src/nexus/onboarding/first-boot-wizard.ts +56 -8
  456. package/src/oauth/codex.ts +7 -0
  457. package/src/onboarding/first-run.ts +37 -6
  458. package/src/onboarding/web-first.ts +43 -11
  459. package/src/ops/overview-collect.ts +30 -9
  460. package/src/ops/overview.ts +22 -9
  461. package/src/persona/loader.ts +11 -0
  462. package/src/persona/types.ts +8 -0
  463. package/src/pr-tool/census.ts +261 -27
  464. package/src/prompt-library/session-guidance.ts +14 -0
  465. package/src/provider-summary.ts +8 -0
  466. package/src/pty-shell/herdr-agent-report.ts +118 -9
  467. package/src/pty-shell/herdr-prompt.ts +266 -0
  468. package/src/pty-shell/pty-control-ipc.ts +151 -8
  469. package/src/pty-shell/pty-manifest.ts +7 -0
  470. package/src/pty-shell/pty-special-keys.ts +42 -2
  471. package/src/pty-shell/stale-install-check.ts +99 -0
  472. package/src/pty-shell/tui-run-state.ts +14 -2
  473. package/src/release-loop/auto-start.ts +20 -2
  474. package/src/release-loop/checklist.ts +100 -4
  475. package/src/release-loop/landed-but-yellow.ts +129 -5
  476. package/src/release-loop/light-rc.ts +1 -0
  477. package/src/release-loop/rehearsal.ts +37 -21
  478. package/src/repl/index.ts +192 -17
  479. package/src/repl/ops-msg-bridge.ts +32 -0
  480. package/src/seat-loop/knowhow-lint.ts +1 -1
  481. package/src/self-dev/ask-launch-flow.ts +44 -5
  482. package/src/self-dev/cli-help-text.ts +17 -0
  483. package/src/self-dev/decompose-proposal.ts +172 -20
  484. package/src/self-dev/decompose.ts +61 -0
  485. package/src/self-dev/dev-cli.ts +48 -12
  486. package/src/self-dev/dev-pipeline.ts +8 -2
  487. package/src/self-dev/draft-residue.ts +37 -1
  488. package/src/self-dev/draft-sweep.ts +137 -11
  489. package/src/self-dev/draft-triage-rules.ts +138 -0
  490. package/src/self-dev/fleet-reduce.ts +3 -2
  491. package/src/self-dev/glm-credential-guard.ts +77 -0
  492. package/src/self-dev/graph-front-nodes.ts +112 -0
  493. package/src/self-dev/graph-journey-nodes.ts +2 -0
  494. package/src/self-dev/graph-journey-route.ts +12 -2
  495. package/src/self-dev/harness-run-cli.ts +3 -15
  496. package/src/self-dev/orchestrate-cli-text.ts +10 -0
  497. package/src/self-dev/orchestrate-cli.ts +68 -3
  498. package/src/self-dev/orchestrate.ts +116 -17
  499. package/src/self-dev/review-provider-fallback.ts +35 -1
  500. package/src/self-dev/run-supervisor.ts +55 -2
  501. package/src/self-dev/run-types.ts +1 -1
  502. package/src/self-dev/self-orchestrate-runtime.ts +1 -11
  503. package/src/self-dev/shard-aggregate.ts +42 -5
  504. package/src/self-dev/shard-run.ts +8 -1
  505. package/src/self-health/autoheal.ts +153 -0
  506. package/src/self-health/signatures.ts +6 -0
  507. package/src/self-implement/blocked-draft-policy.ts +20 -0
  508. package/src/self-implement/classify-shadow.ts +3 -7
  509. package/src/self-implement/device-first.ts +116 -0
  510. package/src/self-implement/doctor-visit.ts +257 -2
  511. package/src/self-implement/evidence-command-budget.ts +80 -0
  512. package/src/self-implement/gate-baseline.ts +56 -17
  513. package/src/self-implement/gate-cli.ts +1 -0
  514. package/src/self-implement/gate-remote.ts +31 -20
  515. package/src/self-implement/goal-author-cli.ts +1 -0
  516. package/src/self-implement/goal-author-decompose.ts +534 -45
  517. package/src/self-implement/goal-author.ts +282 -7
  518. package/src/self-implement/goal-elaborate.ts +449 -0
  519. package/src/self-implement/goal-refine.ts +273 -0
  520. package/src/self-implement/graph-overlay-yaml.ts +14 -3
  521. package/src/self-implement/graph-variant.ts +40 -6
  522. package/src/self-implement/headless-elanous-driver.ts +19 -3
  523. package/src/self-implement/off-diff-evidence.ts +58 -6
  524. package/src/self-implement/orchestrator.ts +370 -62
  525. package/src/self-implement/pod-warm-pool-profile.ts +1 -0
  526. package/src/self-implement/pwa-import-graph.ts +1 -1
  527. package/src/self-implement/rework-budget-judgment.ts +5 -8
  528. package/src/self-implement/rework-policy.ts +30 -0
  529. package/src/self-implement/run-ledger.ts +108 -0
  530. package/src/self-implement/run-stop.ts +10 -1
  531. package/src/self-implement/run-ttl.ts +38 -24
  532. package/src/self-implement/running-runs.ts +29 -1
  533. package/src/self-implement/scope-adjacency.ts +115 -0
  534. package/src/self-implement/seams.ts +187 -28
  535. package/src/self-implement/stop-autoheal.ts +87 -1
  536. package/src/self-implement/wiring-check.ts +358 -0
  537. package/src/session-runtime/index.ts +1 -1
  538. package/src/setup/llm-env-detect.ts +5 -0
  539. package/src/shell-env-bootstrap.ts +11 -4
  540. package/src/skills/runner.ts +4 -2
  541. package/src/skills/tools/agent.ts +408 -40
  542. package/src/skills/tools/msg-ask.ts +100 -0
  543. package/src/skills/tools/ops-ledger.ts +2 -1
  544. package/src/skills/tools/schedule-wakeup.ts +23 -4
  545. package/src/skills/tools/shell.ts +61 -6
  546. package/src/skills/tools/terminal-modal-inject.ts +1 -2
  547. package/src/skills/tools/terminal-modal.ts +10 -8
  548. package/src/skills/url-router.ts +41 -1
  549. package/src/smart-wizard/run.ts +66 -0
  550. package/src/status/bar.ts +34 -6
  551. package/src/status/status-provider-info.ts +37 -6
  552. package/src/steward/hold-hygiene.ts +24 -6
  553. package/src/steward/rejudge-tick.ts +10 -2
  554. package/src/steward/ta-repair-event-collect.ts +17 -6
  555. package/src/steward/triage-budget.ts +194 -0
  556. package/src/steward/triage.ts +20 -3
  557. package/src/task-agent/actions.ts +72 -2
  558. package/src/task-agent/green-sweep.ts +4 -0
  559. package/src/task-agent/land-queue.ts +21 -8
  560. package/src/task-agent/live-moves.ts +182 -10
  561. package/src/task-agent/mission.ts +30 -0
  562. package/src/task-agent/parent-host.ts +123 -3
  563. package/src/task-agent/rejudge-on-head.ts +15 -0
  564. package/src/task-agent/repair-cycle.ts +138 -32
  565. package/src/task-agent/repair-next-move.ts +31 -3
  566. package/src/task-agent/repair-outcomes.ts +29 -0
  567. package/src/task-agent/repair-worker.ts +14 -4
  568. package/src/task-agent/shadow.ts +48 -4
  569. package/src/task-agent/task-card-prefix.ts +2 -0
  570. package/src/task-agent/task-hand.ts +525 -57
  571. package/src/task-agent/task-parent-role-llm.ts +36 -0
  572. package/src/task-agent/tier-split.ts +196 -0
  573. package/src/task-agent/work-order-build.ts +2 -0
  574. package/src/task-agent/work-order.ts +3 -2
  575. package/src/task-orchestrator/generator-prompt.ts +60 -11
  576. package/src/task-orchestrator/surfaces/pod-account-broker.ts +42 -0
  577. package/src/task-orchestrator/surfaces/pod-child-providers.ts +1 -1
  578. package/src/task-orchestrator/surfaces/pod-child-reuse.ts +146 -0
  579. package/src/task-orchestrator/surfaces/pod-command-job.ts +130 -11
  580. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +215 -3
  581. package/src/task-orchestrator/surfaces/pod-image-source.ts +89 -1
  582. package/src/task-orchestrator/surfaces/pod-launch-history.ts +133 -0
  583. package/src/task-orchestrator/surfaces/pod-lease.ts +17 -8
  584. package/src/task-orchestrator/surfaces/pod-manifest-test-normalize.ts +19 -2
  585. package/src/task-orchestrator/surfaces/pod-pool.ts +76 -4
  586. package/src/task-orchestrator/surfaces/pod-provenance.ts +82 -0
  587. package/src/task-orchestrator/surfaces/pod-repair-commit.ts +18 -3
  588. package/src/task-orchestrator/surfaces/pod-si-warm-entry.ts +16 -0
  589. package/src/task-orchestrator/surfaces/pod-si-warm-host.ts +224 -0
  590. package/src/task-orchestrator/surfaces/pod-si-warm-protocol.ts +201 -0
  591. package/src/task-orchestrator/surfaces/pod-si-warm-provider.ts +444 -0
  592. package/src/task-orchestrator/surfaces/pod-si-warm-runner.ts +387 -0
  593. package/src/task-orchestrator/surfaces/pod-si-warm-transport.ts +157 -0
  594. package/src/task-orchestrator/surfaces/pod-source-receive.ts +18 -0
  595. package/src/task-orchestrator/surfaces/pod-warm-provenance.ts +81 -0
  596. package/src/task-orchestrator/surfaces/pod-warm-worker-pod.ts +6 -2
  597. package/src/task-orchestrator/surfaces/pod-warm-worker-protocol.ts +7 -0
  598. package/src/task-orchestrator/surfaces/pod-warm-worker-provider.ts +521 -40
  599. package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +3 -0
  600. package/src/task-orchestrator/surfaces/self-implement-pod.ts +759 -50
  601. package/src/task-orchestrator/surfaces/self-implement.ts +65 -2
  602. package/src/tool-runtime/index.ts +15 -2
  603. package/src/tool-runtime/mcp-proxy-bootstrap.ts +29 -0
  604. package/src/tool-runtime/ops-ledger-runtimes.ts +10 -2
  605. package/src/tty-foreground.ts +155 -0
  606. package/src/tui/README.md +2 -0
  607. package/src/tui/chat/background-job-status.ts +54 -0
  608. package/src/tui/chat/goal-interim.ts +173 -0
  609. package/src/tui/chat/log-view.ts +267 -0
  610. package/src/tui/chat/self-health-status.ts +232 -0
  611. package/src/tui/entry.ts +695 -0
  612. package/src/tui/input/composer.ts +151 -0
  613. package/src/tui/seat-mail-boot.ts +59 -0
  614. package/src/tui/slash/compact-command.ts +7 -0
  615. package/src/tui/slash/compact.ts +94 -0
  616. package/src/tui/slash/new-session.ts +35 -0
  617. package/src/tui/slash/resume.ts +75 -0
  618. package/src/tui-client/local-chat-turn.ts +282 -0
  619. package/src/tui-client/local-harness-command.ts +63 -0
  620. package/src/tui-client/local-provider-command.ts +184 -0
  621. package/src/tui-client/next-dashboard-runtime.ts +8 -0
  622. package/src/tui-client/next-loop-command.ts +70 -0
  623. package/src/tui-client/next-wakeup.ts +191 -0
  624. package/src/tui.ts +37 -0
  625. package/src/ui/modal-adapter.ts +2 -2
  626. package/src/user-config.ts +4 -2
  627. package/src/ux-sim/classic-tui-seat-scenarios.ts +516 -0
  628. package/src/ux-sim/cli-config-audit-device.ts +121 -0
  629. package/src/ux-sim/dashboard-event-loop-stall-monitor-device.ts +238 -0
  630. package/src/ux-sim/event-monkey.ts +379 -0
  631. package/src/ux-sim/fake-bin.ts +87 -7
  632. package/src/ux-sim/fake-herdr.ts +238 -0
  633. package/src/ux-sim/fake-llm.ts +111 -6
  634. package/src/ux-sim/fault-glm-no-credential.ts +116 -0
  635. package/src/ux-sim/fault-nextmove-dispatch.ts +367 -0
  636. package/src/ux-sim/fault-pod-codex-503.ts +211 -0
  637. package/src/ux-sim/fault-scope-escape-lift.ts +216 -0
  638. package/src/ux-sim/fault-tui-400-autoheal.ts +297 -0
  639. package/src/ux-sim/goal-device.ts +619 -0
  640. package/src/ux-sim/in-process-cli.ts +22 -5
  641. package/src/ux-sim/mock-authoring-first-run-child.ts +35 -0
  642. package/src/ux-sim/mock-authoring-first-run.ts +97 -0
  643. package/src/ux-sim/mock-seat-pty-frame-child.ts +29 -0
  644. package/src/ux-sim/mock-seat-pty-frame.ts +297 -0
  645. package/src/ux-sim/mock-seat-pty-sequence-child.ts +21 -0
  646. package/src/ux-sim/mock-seat-pty-sequence.ts +206 -0
  647. package/src/ux-sim/next-tui-seat-scenarios.ts +631 -0
  648. package/src/ux-sim/onboarding-first-run-device.ts +269 -0
  649. package/src/ux-sim/pty-key-modifiers-device.ts +85 -0
  650. package/src/ux-sim/subagent-tools-device.ts +81 -0
  651. package/src/ux-sim/task-orchestrator-surfaces-self-implement-pod-device.ts +250 -0
  652. package/src/ux-sim/tty-foreground-steal-child.ts +21 -0
  653. package/src/version/packed-revision.json +1 -1
  654. package/apps/pwa/out/_next/static/chunks/1162.c69abda138c7ce43.js +0 -79
  655. package/apps/pwa/out/_next/static/chunks/1449-1fede62af84c3e43.js +0 -1
  656. package/apps/pwa/out/_next/static/chunks/1598-13fc845935e816df.js +0 -1
  657. package/apps/pwa/out/_next/static/chunks/1639.62c5e16fa20d242f.js +0 -94
  658. package/apps/pwa/out/_next/static/chunks/1661-adb8b2babc9aaf8c.js +0 -1
  659. package/apps/pwa/out/_next/static/chunks/2147.107c0fc6146fb15a.js +0 -23
  660. package/apps/pwa/out/_next/static/chunks/2202-ef1c9b000c3074fc.js +0 -1
  661. package/apps/pwa/out/_next/static/chunks/2247.1e326638ee43e672.js +0 -115
  662. package/apps/pwa/out/_next/static/chunks/2268.56e1e3d58180abae.js +0 -8
  663. package/apps/pwa/out/_next/static/chunks/2469-606819c812414021.js +0 -1
  664. package/apps/pwa/out/_next/static/chunks/2593.e53c1620c0bb3599.js +0 -1
  665. package/apps/pwa/out/_next/static/chunks/2690.e96671114222676a.js +0 -1
  666. package/apps/pwa/out/_next/static/chunks/2898-79ee1a5cc0cd9d13.js +0 -1
  667. package/apps/pwa/out/_next/static/chunks/298.26099a9f65e5c8ff.js +0 -1
  668. package/apps/pwa/out/_next/static/chunks/2987-30cf9c2eb7b8bdd5.js +0 -1
  669. package/apps/pwa/out/_next/static/chunks/2992.51fc2a7295bd8139.js +0 -1
  670. package/apps/pwa/out/_next/static/chunks/3001-5a52788c813b6113.js +0 -1
  671. package/apps/pwa/out/_next/static/chunks/3014-291245fba9df7b5b.js +0 -1
  672. package/apps/pwa/out/_next/static/chunks/3068.ee78201f0251a18d.js +0 -1
  673. package/apps/pwa/out/_next/static/chunks/3083.cd399a17e9365f78.js +0 -1
  674. package/apps/pwa/out/_next/static/chunks/3117-e1c474683fd1f824.js +0 -1
  675. package/apps/pwa/out/_next/static/chunks/3251-2c17bd5a91f8de2e.js +0 -1
  676. package/apps/pwa/out/_next/static/chunks/326.3fb32322bb2ddf6d.js +0 -1
  677. package/apps/pwa/out/_next/static/chunks/3421-7ff7fbaa156985a7.js +0 -1
  678. package/apps/pwa/out/_next/static/chunks/381.f55dc63ea87735e6.js +0 -1
  679. package/apps/pwa/out/_next/static/chunks/4129-ab0a6189f1778633.js +0 -1
  680. package/apps/pwa/out/_next/static/chunks/4361.93d14ba594b3a035.js +0 -104
  681. package/apps/pwa/out/_next/static/chunks/4364.7a7eb19cceb67d2f.js +0 -286
  682. package/apps/pwa/out/_next/static/chunks/4406.d71f506f7c3ee9d3.js +0 -149
  683. package/apps/pwa/out/_next/static/chunks/4461.2ad1514180ae7e29.js +0 -148
  684. package/apps/pwa/out/_next/static/chunks/4479.bd309cbe0e3842ee.js +0 -64
  685. package/apps/pwa/out/_next/static/chunks/4522-05966f777d66d13b.js +0 -1
  686. package/apps/pwa/out/_next/static/chunks/4543-9a3e9812e845de65.js +0 -1
  687. package/apps/pwa/out/_next/static/chunks/4557.f8f1d84a0d90d590.js +0 -74
  688. package/apps/pwa/out/_next/static/chunks/4589-79b11542179ef58c.js +0 -1
  689. package/apps/pwa/out/_next/static/chunks/4633-9f8f9f88ffbb8f5c.js +0 -1
  690. package/apps/pwa/out/_next/static/chunks/4831.23af4d10c0697b5e.js +0 -1
  691. package/apps/pwa/out/_next/static/chunks/4983.5951b85724f61f2a.js +0 -1
  692. package/apps/pwa/out/_next/static/chunks/5028-f0722e6114c313e7.js +0 -1
  693. package/apps/pwa/out/_next/static/chunks/5249.96a848656b582a93.js +0 -1
  694. package/apps/pwa/out/_next/static/chunks/5311.16e0ffbfa5468b80.js +0 -62
  695. package/apps/pwa/out/_next/static/chunks/5489.ec2dd1b5272a19dc.js +0 -24
  696. package/apps/pwa/out/_next/static/chunks/559.df87695df637920c.js +0 -1
  697. package/apps/pwa/out/_next/static/chunks/5618-5c88f2f70178c4ad.js +0 -1
  698. package/apps/pwa/out/_next/static/chunks/5764.05487bf0b73ddb6b.js +0 -1
  699. package/apps/pwa/out/_next/static/chunks/5930.e678826c3f831aab.js +0 -1
  700. package/apps/pwa/out/_next/static/chunks/5dfb57f7.f2e3305326202474.js +0 -1
  701. package/apps/pwa/out/_next/static/chunks/6053-d893974de92e6198.js +0 -1
  702. package/apps/pwa/out/_next/static/chunks/6200-cefd6f33f59d765e.js +0 -1
  703. package/apps/pwa/out/_next/static/chunks/6314-edeba650ae686ac9.js +0 -1
  704. package/apps/pwa/out/_next/static/chunks/6316-bc9a9b74d7842d79.js +0 -1
  705. package/apps/pwa/out/_next/static/chunks/6476-13b9779b73528008.js +0 -1
  706. package/apps/pwa/out/_next/static/chunks/666-04db3ac4c8e3c66f.js +0 -1
  707. package/apps/pwa/out/_next/static/chunks/670.682e55106ef915a6.js +0 -77
  708. package/apps/pwa/out/_next/static/chunks/6816.b7ba9d0a1a4a46a7.js +0 -1
  709. package/apps/pwa/out/_next/static/chunks/690-fbbcc777f189af37.js +0 -1
  710. package/apps/pwa/out/_next/static/chunks/6989.0d3d4020e5cb3df6.js +0 -90
  711. package/apps/pwa/out/_next/static/chunks/7193-cf355cca5ba6c5d5.js +0 -1
  712. package/apps/pwa/out/_next/static/chunks/7217-d17393fe7af78fa6.js +0 -1
  713. package/apps/pwa/out/_next/static/chunks/7232.24edbd938a69c8d0.js +0 -1
  714. package/apps/pwa/out/_next/static/chunks/7295cd1e.b4b557908888eeee.js +0 -1
  715. package/apps/pwa/out/_next/static/chunks/7323.adb02de21991883c.js +0 -1
  716. package/apps/pwa/out/_next/static/chunks/7386.5d8e7e42e818dfb6.js +0 -287
  717. package/apps/pwa/out/_next/static/chunks/7448.478958ffbbba0ac0.js +0 -1
  718. package/apps/pwa/out/_next/static/chunks/7537.ba2208c5c656b8a3.js +0 -1
  719. package/apps/pwa/out/_next/static/chunks/7596-962ae6db6cfc9c83.js +0 -1
  720. package/apps/pwa/out/_next/static/chunks/7671.cff51beac212a2ba.js +0 -1
  721. package/apps/pwa/out/_next/static/chunks/7883.3916734ee16d3e58.js +0 -27
  722. package/apps/pwa/out/_next/static/chunks/7952.b192c31b6a0e00cc.js +0 -1
  723. package/apps/pwa/out/_next/static/chunks/801.5b24e81eded22d1b.js +0 -1
  724. package/apps/pwa/out/_next/static/chunks/8176.8c9a8613f5a01ac1.js +0 -36
  725. package/apps/pwa/out/_next/static/chunks/819e78a6.fa3dd7f7da19bf61.js +0 -1
  726. package/apps/pwa/out/_next/static/chunks/8220-78978450c3183d8d.js +0 -1
  727. package/apps/pwa/out/_next/static/chunks/8221-eb60b5bc692a3361.js +0 -1
  728. package/apps/pwa/out/_next/static/chunks/826.e9a26064f474949e.js +0 -1
  729. package/apps/pwa/out/_next/static/chunks/8459-a351487a4480f584.js +0 -1
  730. package/apps/pwa/out/_next/static/chunks/8482-75823d513a4ce151.js +0 -1
  731. package/apps/pwa/out/_next/static/chunks/8488.8da515248cda3f3a.js +0 -1
  732. package/apps/pwa/out/_next/static/chunks/8584.388a1779d9b203af.js +0 -1
  733. package/apps/pwa/out/_next/static/chunks/8637.a55e098e61ffefb8.js +0 -1
  734. package/apps/pwa/out/_next/static/chunks/9207-8e6a12db30373f32.js +0 -1
  735. package/apps/pwa/out/_next/static/chunks/9263-00ae932c62696a86.js +0 -1
  736. package/apps/pwa/out/_next/static/chunks/9318.caafc3a130e8769c.js +0 -1
  737. package/apps/pwa/out/_next/static/chunks/932.da3fb55951882562.js +0 -93
  738. package/apps/pwa/out/_next/static/chunks/9433.002f8426ac75b6b7.js +0 -1
  739. package/apps/pwa/out/_next/static/chunks/9464.dd631a5a14b1d660.js +0 -24
  740. package/apps/pwa/out/_next/static/chunks/9739.8d5f36ac67a3b236.js +0 -1
  741. package/apps/pwa/out/_next/static/chunks/9743.beeabb9d5f86abe4.js +0 -1
  742. package/apps/pwa/out/_next/static/chunks/9843-a23d3f32dc43f715.js +0 -1
  743. package/apps/pwa/out/_next/static/chunks/994-28211077c7c587d5.js +0 -1
  744. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-3b4c947f174e33d8.js +0 -1
  745. package/apps/pwa/out/_next/static/chunks/app/approvals/page-369d5ce99483f859.js +0 -1
  746. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-63c0ed0eb26e5fce.js +0 -1
  747. package/apps/pwa/out/_next/static/chunks/app/board/page-62802c6003bc85cc.js +0 -1
  748. package/apps/pwa/out/_next/static/chunks/app/botlab/page-ecb41560d4e77be9.js +0 -1
  749. package/apps/pwa/out/_next/static/chunks/app/bots/page-7b65ad8130c93e95.js +0 -1
  750. package/apps/pwa/out/_next/static/chunks/app/ceo/page-8755477c15a9060c.js +0 -1
  751. package/apps/pwa/out/_next/static/chunks/app/chat/page-5725447dd972c471.js +0 -1
  752. package/apps/pwa/out/_next/static/chunks/app/consult/page-b979de0591a69375.js +0 -1
  753. package/apps/pwa/out/_next/static/chunks/app/control/page-6d0796bf4633751a.js +0 -1
  754. package/apps/pwa/out/_next/static/chunks/app/decisions/page-59c31dc0394e67b4.js +0 -1
  755. package/apps/pwa/out/_next/static/chunks/app/design-check/page-12dfc95f6cc5c718.js +0 -1
  756. package/apps/pwa/out/_next/static/chunks/app/editor/page-25788244e66aa4dd.js +0 -1
  757. package/apps/pwa/out/_next/static/chunks/app/exec/page-a2555dc705466322.js +0 -1
  758. package/apps/pwa/out/_next/static/chunks/app/healthz/route-3b4c947f174e33d8.js +0 -1
  759. package/apps/pwa/out/_next/static/chunks/app/inside/page-578729adbeaa40cb.js +0 -1
  760. package/apps/pwa/out/_next/static/chunks/app/intake/page-a1d20fffbec35205.js +0 -1
  761. package/apps/pwa/out/_next/static/chunks/app/layout-4a558f94516b8004.js +0 -1
  762. package/apps/pwa/out/_next/static/chunks/app/live/page-9e3834705b86145e.js +0 -1
  763. package/apps/pwa/out/_next/static/chunks/app/live-run/page-d7bff0f0905a662c.js +0 -1
  764. package/apps/pwa/out/_next/static/chunks/app/loops/page-cb1c5fe746ea4014.js +0 -1
  765. package/apps/pwa/out/_next/static/chunks/app/market/page-9ee320507356582a.js +0 -1
  766. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66fd183b74835886.js +0 -1
  767. package/apps/pwa/out/_next/static/chunks/app/morning/page-3272a064a01a9927.js +0 -1
  768. package/apps/pwa/out/_next/static/chunks/app/not-found-f281b7a7f6ef2529.js +0 -1
  769. package/apps/pwa/out/_next/static/chunks/app/observatory/page-d45a65f8b7124fcd.js +0 -1
  770. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-e9c1fe684dd19a66.js +0 -1
  771. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-b8322932fc6e34de.js +0 -1
  772. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-82c66d9af2290575.js +0 -1
  773. package/apps/pwa/out/_next/static/chunks/app/outputs/page-63c6752009c97b05.js +0 -1
  774. package/apps/pwa/out/_next/static/chunks/app/page-bb33d42ebc358aec.js +0 -1
  775. package/apps/pwa/out/_next/static/chunks/app/reflection/page-7e4c9379197d7214.js +0 -1
  776. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-38bf1e9a7001732b.js +0 -1
  777. package/apps/pwa/out/_next/static/chunks/app/sessions/page-2e5b752012389d56.js +0 -1
  778. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-398c0faa173203f7.js +0 -1
  779. package/apps/pwa/out/_next/static/chunks/app/settings/page-002f53a965b6445c.js +0 -1
  780. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-7f75997f61a26269.js +0 -1
  781. package/apps/pwa/out/_next/static/chunks/app/setup/layout-3b4c947f174e33d8.js +0 -1
  782. package/apps/pwa/out/_next/static/chunks/app/setup/page-05631753294f23fb.js +0 -1
  783. package/apps/pwa/out/_next/static/chunks/app/share/page-edd700fd2cc3d7f8.js +0 -1
  784. package/apps/pwa/out/_next/static/chunks/app/showroom/page-105f83ecb4594365.js +0 -1
  785. package/apps/pwa/out/_next/static/chunks/app/tasks/page-5823e87d2017f19c.js +0 -1
  786. package/apps/pwa/out/_next/static/chunks/app/term/page-df3445b207ceceab.js +0 -1
  787. package/apps/pwa/out/_next/static/chunks/app/today/page-b51ca09566c03de7.js +0 -1
  788. package/apps/pwa/out/_next/static/chunks/app/trace/page-8aacd5ded5dbac60.js +0 -1
  789. package/apps/pwa/out/_next/static/chunks/app/vault/page-bae3365a0b3c6a8e.js +0 -1
  790. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-9ed52b48ff1aa7fd.js +0 -1
  791. package/apps/pwa/out/_next/static/chunks/app/workflows/page-b6f8cad35fe00946.js +0 -1
  792. package/apps/pwa/out/_next/static/chunks/app/workspace/page-6d25ceda500d2457.js +0 -1
  793. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-b6401a5f2c962fe8.js +0 -1
  794. package/apps/pwa/out/_next/static/chunks/ebf6cf5e.0d2f887b040e80fb.js +0 -122
  795. package/apps/pwa/out/_next/static/chunks/f09ec7d7-d29abe23edcbb193.js +0 -1
  796. package/apps/pwa/out/_next/static/chunks/framework-9322cb8a56ac081c.js +0 -1
  797. package/apps/pwa/out/_next/static/chunks/main-2fbab294e49a1561.js +0 -1
  798. package/apps/pwa/out/_next/static/chunks/main-app-f36729f56621a8f3.js +0 -1
  799. package/apps/pwa/out/_next/static/chunks/pages/_app-2a62e450b47c6114.js +0 -1
  800. package/apps/pwa/out/_next/static/chunks/pages/_error-5e17f7b445f34d43.js +0 -1
  801. package/apps/pwa/out/_next/static/chunks/webpack-b4263e2190ea7adf.js +0 -1
  802. /package/apps/pwa/out/_next/static/{L04y73dlwKeTW-GetaTF3 → uC-N0emedeNOFuaBQFv-I}/_ssgManifest.js +0 -0
@@ -4,7 +4,7 @@ import { appendFileSync, chmodSync, copyFileSync, existsSync, mkdirSync, mkdtemp
4
4
  import { hostname, tmpdir } from 'node:os';
5
5
  import { createHash, randomUUID } from 'node:crypto';
6
6
  import { dirname, join, resolve } from 'node:path';
7
- import { debug } from '../../src/debug/log.js';
7
+ import { debug, redactSecretText } from '../../src/debug/log.js';
8
8
  import { registerStandaloneLogSink } from '../../src/domains/standalone-log-sink.js';
9
9
  import { landingFreezeMessage, readLandingFreeze } from '../../src/release-loop/landing-freeze.js';
10
10
  import { effectiveInstanceRoot, releaseLedgerRoot } from '../../src/instance/resolve.js';
@@ -12,9 +12,14 @@ import { getElanousConfigDirOverride } from '../../src/elanous-config-dir.js';
12
12
  import { diffFailures, junitFailures, parseFailures, parseTimedOutFailures } from './gate-diff';
13
13
  import { loadEnvKnownFailures, splitKnownEnv } from './env-known-failures';
14
14
  import { loadGateBatchSafe } from './gate-batch-safe';
15
+ import { gateHistory } from './gate-history.js';
15
16
  import { baselineFromCutLogs } from './baseline-from-logs';
16
- import { mergePartialFailures, readPartialPlan, testFileOfId, verifyDocsOnlyCarry } from './gate-partial';
17
+ import { mergePartialFailures, readPartialPlanResult, testFileOfId, verifyDocsOnlyCarry } from './gate-partial';
17
18
  import { planGateRadiusShadow, shadowActual, writeGateRadiusShadow, type GateRadiusShadowPlan } from './gate-radius-shadow';
19
+ import { planHardRehearsalRadius, partialGateImagesMatch, preflightGateImageDigest } from './gate-radius-live';
20
+ import { actualGatePodImageDigest, gateBaselineReceipt, gateEnvFingerprint, gateGraphPins, gateSourcePins,
21
+ POD_SWEEP_INTEGRATION_ONLY } from './gate-baseline-receipt';
22
+ export { POD_SWEEP_INTEGRATION_ONLY } from './gate-baseline-receipt';
18
23
  import { logPlanWalltime, planDurations, readFileMemory } from './shard-plan';
19
24
  import { defaultGateHostMirrorReaders, resolveGateHostMirror, type GateHostMirror, type GateHostMirrorReaders } from './gate-host-mirror';
20
25
  import { isGateSpawnLaneFile, planGateSpawnSeparatedShards } from './spawn-lane';
@@ -67,6 +72,8 @@ export function warmCommandMatchesRequest(command: WarmWorkerCommand, request: O
67
72
  }
68
73
 
69
74
  interface CommandResult { rc: number; output: string; passedIds?: string[]; stalledEnv?: Array<{ file: string; reason: 'no-output' }>; timedOut?: boolean;
75
+ /** Identity and complete selected inventory of this Pod sweep; only the producer may issue it. */
76
+ podSweep?: { id: string; shards: number; files: string[] };
70
77
  /** Complete, source-bound one-file Job result for shadow comparison; absent means unmeasured. */
71
78
  warmReference?: WarmWorkerFileResult | null;
72
79
  warmAttemptId?: string;
@@ -74,7 +81,35 @@ interface CommandResult { rc: number; output: string; passedIds?: string[]; stal
74
81
  /** GATE-POD-STALL-FAILFAST ① — shard Pods whose tests the mid-run watch aborted in this sweep. */
75
82
  abortedShards?: number;
76
83
  /** Each contributing Pod leaf already passed failuresOf; repeated printed ids cannot add new failures. */
77
- validatedShards?: boolean }
84
+ validatedShards?: boolean
85
+ /** GATE-RISK-LANE — the watch-list shard this sweep ran first, with its plan and measurement (observation only). */
86
+ riskLane?: GateRiskLaneRecord }
87
+ function redactedGateLogTail(input: string): string {
88
+ // Redact before trimming: a truncated token might no longer match the redaction rule.
89
+ const bytes = Buffer.from(redactSecretText(input), 'utf8');
90
+ let start = Math.max(0, bytes.length - 16_384);
91
+ while (start < bytes.length && (bytes[start]! & 0xc0) === 0x80) start++;
92
+ return bytes.subarray(start).toString('utf8');
93
+ }
94
+ /**
95
+ * GATE-FLOW-SIM — the gate's outside world as one seam: a scenario hands in the repo's fake clock
96
+ * (`src/ux-sim/fake-clock.ts`) plus fake Pod/kubectl/ssh tools, and the production entry hands in nothing.
97
+ * Every field falls back to the real tool, so an absent seam is today's behaviour byte for byte.
98
+ */
99
+ export interface GateTools {
100
+ /** Wall clock the gate measures with; absent = `Date.now`. */
101
+ now?: () => number;
102
+ /** The Pod Job runner (the fake Pod stage of a scenario); absent = the positional `podCommand`, else `runPodCommand`. */
103
+ podCommand?: (options: RunPodCommandOptions) => Promise<PodCommandResult>;
104
+ /** Kubernetes client (pool readiness · Pod lifecycle · Job log tail · failure evidence); absent = `defaultKubectl`. */
105
+ kubectl?: typeof defaultKubectl;
106
+ /** SSH transport behind {@link GateRunner.command} on a remote host; absent = a real `ssh` child. */
107
+ ssh?: (host: string, script: string, limitMs?: number) => { rc: number; output: string; timedOut?: boolean };
108
+ /** SSH reachability probe of a multi-host roster; absent = `defaultRemoteRun`. */
109
+ remoteRun?: typeof defaultRemoteRun;
110
+ /** Pool admission a shard waits on (leases ⊕ a cluster reading); absent = the gate's own pool admission. */
111
+ admission?: GateAdmission;
112
+ }
78
113
  export interface GateRunner {
79
114
  command(cmd: string, args: string[], cwd: string, limitMs?: number): Promise<CommandResult>;
80
115
  localCommand(cmd: string, args: string[], cwd: string, limitMs?: number): Promise<CommandResult>;
@@ -90,9 +125,15 @@ export interface GateRunner {
90
125
  removeSnapshot(tree: string): Promise<void>;
91
126
  /** Absolute remote bare mirror path, set before preparing the remote trees. */
92
127
  remoteMirror?: string;
128
+ /** GATE-FLOW-SIM — the clock this runner measures with (the injected fake clock in a scenario); absent = `Date.now`. */
129
+ now?(): number;
93
130
  }
94
131
  export interface GateOptions {
95
132
  commit: string;
133
+ /** The graph run that launched this gate; direct hard rehearsal instead pins source bytes explicitly. */
134
+ graphRunId?: string;
135
+ /** Only a direct hard rehearsal may pin graph bytes from its measured Git commit. */
136
+ rehearsalBaseline?: boolean;
96
137
  forceFreeze?: boolean;
97
138
  version: string;
98
139
  baselineVersion?: string;
@@ -113,6 +154,8 @@ export interface GateOptions {
113
154
  /** Opt-in gate shadow: provider fills the fenced lease identity. The Job remains authoritative. */
114
155
  warmReplay?: (input: Omit<WarmWorkerCommand, 'leaseId' | 'generation' | 'workerId' | 'key'>) => Promise<{ command: WarmWorkerCommand | null; transport: WarmPodTransportResult }>;
115
156
  warmQuarantine?: (command: WarmWorkerCommand, reason: string) => Promise<void>;
157
+ /** Explicit gate-profile producer handoff; shadow-only callers keep their Job path. */
158
+ warmProducerEnabled?: boolean;
116
159
  warmFinalize?: (command: WarmWorkerCommand) => Promise<void>;
117
160
  /** Set only on the cut recheck; baseline Jobs never consume the cut worker. */
118
161
  warmCutSha?: string;
@@ -154,6 +197,9 @@ export interface GateOptions {
154
197
  liveLog?: (job: { name: string; context?: string }) => string | null;
155
198
  /** false = the stop did not take; the watch asks again every poll until it does (void = taken). */
156
199
  stopJob?: (job: { name: string; context?: string }) => boolean | void;
200
+ /** Failure-only Pod status and log reader; injected tests never call the live cluster. */
201
+ failureEvidence?: (job: { name: string; context?: string }) => { podName?: string; termination?: { reason?: string; message?: string; exitCode?: number; finishedAt?: string }; logTail?: string; error?: string };
202
+ failureKubectl?: typeof defaultKubectl;
157
203
  /** How often the stall watch reads the live log (ms); omitted = {@link GATE_POD_STALL_POLL_MS}. */
158
204
  stallPollMs?: number;
159
205
  /** GATE-POD-STALL-FAILFAST ① — seconds a shard may go without its part logs growing once test output began, before
@@ -165,6 +211,8 @@ export interface GateOptions {
165
211
  abortTests?: (job: { name: string; context?: string }, reason: string) => boolean | void;
166
212
  /** GATE-LIVE-OBS — the shard state file (`gateShardsPath(version)`) this sweep keeps current; absent = not written. */
167
213
  shardsFile?: { path: string; version: string };
214
+ /** GATE-RISK-LANE — the ledgers `gateHistory` reads its watch list from; absent = no risk lane (the sweep plans as before). */
215
+ riskWatch?: { instanceRoots: readonly string[]; ledgerRoots: readonly string[] };
168
216
  /** GATE-MULTI-HOST — is this pool member reachable (kubectl ns ⊕ ssh)? Injected by tests; absent = the real probe on
169
217
  * the gate's own pool · an injected pool without it never marks a member down (unchanged). */
170
218
  probeMember?: (member: PodPoolMember) => boolean;
@@ -182,7 +230,7 @@ export function withWarmGateHooks(opts: GateOptions, config: unknown, injectedRu
182
230
  && config.pod && typeof config.pod === 'object' && 'warmReuse' in config.pod
183
231
  ? config.pod.warmReuse as WarmReuseSwitchConfig : undefined;
184
232
  return { ...opts, pod: { ...opts.pod, warmReplay: requestWarmPodReplay,
185
- warmFinalize: finalizeWarmPodReplay, warmQuarantine: quarantineWarmPodReplay, warmReuseConfig } };
233
+ warmFinalize: finalizeWarmPodReplay, warmQuarantine: quarantineWarmPodReplay, warmProducerEnabled: true, warmReuseConfig } };
186
234
  }
187
235
  export interface GateResult {
188
236
  outcome: 'ok' | 'regression' | 'error';
@@ -218,6 +266,10 @@ export interface GateResult {
218
266
  /** GATE-POD-STALL-FAILFAST — the shard (and its one fresh retry) printed no test output within the stall window: unmeasured. */
219
267
  stall?: { seconds: number; sinceCheckoutMs: number } }>;
220
268
  partialSummary?: { pass: number; fail: number; errors: number; ran: number; files: number };
269
+ /** Number of complete shards retained when an incomplete sweep is resumed. */
270
+ preservedShardCount?: number;
271
+ /** Completed shard measurements retained when another shard stalled, so same-commit resume can reuse them safely. */
272
+ partialFailures?: SweepFailures;
221
273
  /** GATE-PARTIAL — this verdict re-ran only these files and carried the prior gate's results for the rest. */
222
274
  partial?: { priorCommit: string; files: number };
223
275
  /** GATE-INTRO-RECHECK — new failures that were only the «timed out» shape and passed one isolated re-run with a
@@ -507,6 +559,71 @@ export function shardParts(paths: readonly string[], seconds: (file: string) =>
507
559
  if (current.length) parts.push(current);
508
560
  return parts;
509
561
  }
562
+ /** GATE-RISK-LANE — the watched files this sweep ran first, and how that shard was planned and measured. */
563
+ export interface GateRiskLaneRecord { files: string[]; plannedSeconds: number; durationMs: number; rc: 0 | 1 | null }
564
+
565
+ /** GATE-RISK-LANE — the shard plan plus the risk shard it may have placed first. */
566
+ export interface GateRiskLanePlan {
567
+ /** Execution order: the risk shard first (absent when nothing is watched), then the current plan unchanged. */
568
+ shards: Array<{ files: string[]; plannedSeconds: number }>;
569
+ /** Assignable files this plan treated as risk — exactly `shards[0].files`; empty means «the current plan». */
570
+ riskFiles: string[];
571
+ riskPlannedSeconds: number;
572
+ spawnFiles: string[];
573
+ spawnShardCount: number;
574
+ }
575
+
576
+ /**
577
+ * GATE-RISK-LANE — the watch list the gate-history table already keeps (`gateHistory().watchList`: introduced or flaky
578
+ * in at least two of the last three releases). Reading it never blocks a gate: an unreadable ledger set is «no list».
579
+ */
580
+ export function readGateRiskWatchList(o: { instanceRoots: readonly string[]; ledgerRoots: readonly string[] }): string[] {
581
+ try { return gateHistory({ instanceRoots: o.instanceRoots, ledgerRoots: o.ledgerRoots }).watchList; }
582
+ catch (error) {
583
+ debug.log('release-loop.gate', 'risk-lane-watch-unavailable', { reason: String(error) });
584
+ return [];
585
+ }
586
+ }
587
+
588
+ /**
589
+ * GATE-RISK-LANE (0.2.36) — the next full sweep runs the files the watch list names in its first shard, so a repeat
590
+ * regression breaks before the general shards spend their deadline. Only the order changes:
591
+ * - a watched heavy file already runs alone at the isolation limit, and a watched spawn file keeps its own lane (a part
592
+ * mixing real child/PTY files with regular ones is refused by {@link gateShardShell}) — neither joins the risk shard;
593
+ * - every assignable file is assigned exactly once: the risk files leave the lane they would have been balanced into;
594
+ * - with no watch list, or one that names no file of this sweep, the plan is today's plan byte for byte;
595
+ * - when the watch list hits, the sweep runs exactly one more shard than configured (the risk shard in front), which is
596
+ * the same trade the configuration already makes for spawning a Pool Job.
597
+ */
598
+ export function planGateRiskLaneShards(o: { assignable: readonly string[]; durations: Map<string, number>; shardCount: number;
599
+ heavy: ReadonlySet<string>; watchList: readonly string[] }): GateRiskLanePlan {
600
+ const light = o.assignable.filter((file) => !o.heavy.has(file));
601
+ const watched = new Set(o.watchList);
602
+ const riskFiles = [...new Set(light)].filter((file) => watched.has(file) && !isGateSpawnLaneFile(file)).sort();
603
+ const heavyShards = [...o.heavy].map((file) => ({ files: [file], plannedSeconds: o.durations.get(file) ?? 0 }));
604
+ const planWith = (files: readonly string[]) => planGateSpawnSeparatedShards(files, o.durations, o.shardCount);
605
+ if (!riskFiles.length) {
606
+ const current = planWith(light);
607
+ return { shards: [...current.shards, ...heavyShards], riskFiles: [], riskPlannedSeconds: 0,
608
+ spawnFiles: current.spawnFiles, spawnShardCount: current.spawnShardCount };
609
+ }
610
+ const risk = new Set(riskFiles);
611
+ const rest = planWith(light.filter((file) => !risk.has(file)));
612
+ // An unmeasured file takes the same median weight planShards gives it, so the shard's plan stays comparable.
613
+ const known = o.assignable.map((file) => o.durations.get(file))
614
+ .filter((value): value is number => value !== undefined && Number.isFinite(value) && value >= 0).sort((a, b) => a - b);
615
+ const median = known.length ? (known[Math.floor((known.length - 1) / 2)]! + known[Math.floor(known.length / 2)]!) / 2 : 1;
616
+ const plannedSeconds = riskFiles.reduce((sum, file) => {
617
+ const value = o.durations.get(file);
618
+ return sum + (value !== undefined && Number.isFinite(value) && value >= 0 ? value : median);
619
+ }, 0);
620
+ return {
621
+ shards: [{ files: [...riskFiles], plannedSeconds }, ...rest.shards, ...heavyShards],
622
+ riskFiles: [...riskFiles], riskPlannedSeconds: plannedSeconds,
623
+ spawnFiles: rest.spawnFiles, spawnShardCount: rest.spawnShardCount,
624
+ };
625
+ }
626
+
510
627
  /**
511
628
  * GATE-ENV-BASELINE (W5 · 10-15) — the gate shard Pod's deterministic test env, exported before anything runs.
512
629
  * 0.2.29 gate failures were the Pod's «environment», not the code: no LLM provider · `man` stubbed · CLI spawn timeouts.
@@ -530,7 +647,8 @@ export function gateEnvBaselineLine(env: Readonly<Record<string, string>> = gate
530
647
  * `shard.timeout` so the artifact reader does not mistake an unfinished child for a test verdict.
531
648
  * A part without `.rc` did not finish. The shell itself always exits 0 (as before).
532
649
  */
533
- export function gateShardShell(o: { parts: readonly (readonly string[])[]; cdpPatterns: readonly string[]; softSeconds: number; cachePrefix: string; installSlots: number; depsDir?: string; beatSeconds?: number; parallelParts?: number; testTimeoutMs?: number; partLimitSeconds?: number; sourceBinding?: { cutSha: string; attemptId: string } }): string {
650
+ export function gateShardShell(o: { parts: readonly (readonly string[])[]; cdpPatterns: readonly string[]; softSeconds: number; cachePrefix: string; installSlots: number; depsDir?: string; beatSeconds?: number; parallelParts?: number; testTimeoutMs?: number; partLimitSeconds?: number; rulesBaseSha?: string; sourceBinding?: { cutSha: string; attemptId: string } }): string {
651
+ if (o.rulesBaseSha !== undefined && !/^[0-9a-f]{40}$/.test(o.rulesBaseSha)) throw new Error('invalid rules gate base SHA');
534
652
  // The planner keeps spawn files in their own shards; reject a mixed part rather than silently lose its limit.
535
653
  for (const part of o.parts) {
536
654
  if (part.some(isGateSpawnLaneFile) && !part.every(isGateSpawnLaneFile)) throw new Error('gate spawn lane part mixed with regular tests');
@@ -573,6 +691,7 @@ export function gateShardShell(o: { parts: readonly (readonly string[])[]; cdpPa
573
691
  ...(o.beatSeconds ? gateBeatScript(o.beatSeconds, o.installSlots > 0) : []),
574
692
  ...(o.cachePrefix ? [o.cachePrefix] : []),
575
693
  gateEnvBaselineLine(),
694
+ ...(o.rulesBaseSha ? [`export ELANOUS_GATE_BASE_SHA=${quote(o.rulesBaseSha)}`] : []),
576
695
  // Seconds since this container's PID 1 started (the clone ran before this shell) — 0 when /proc cannot tell.
577
696
  `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)`,
578
697
  'case "$pod_age" in ""|*[!0-9]*) pod_age=0;; esac',
@@ -971,7 +1090,7 @@ export function readShardArtifacts(dir: string, parts: readonly (readonly string
971
1090
  export const gateUnfinishedHint = (relativePath: string, bytes: number) => `[gate] unfinished: ${relativePath} · ${bytes}B (진행 중 part 출력은 그 파일)`;
972
1091
 
973
1092
  // review-model-ab: Pod 에서 출력 없이 멈춤 2/2(10-04 0.2.11 · 로컬 단독 4/0 · 1.7s) — GATE-STALL 수리 전까지 Pod 밖.
974
- export const POD_SWEEP_INTEGRATION_ONLY: readonly string[] = ['scripts/install.test.ts', 'scripts/review-model-ab.test.ts'];
1093
+ // The shared exclusion list lives in gate-baseline-receipt so radius verification uses the producer's rule.
975
1094
  /** Nightly-only whole-repository cases; gate runs the fixture cases in these same files. */
976
1095
  export const GATE_NIGHTLY_AUDITS: readonly string[] = [
977
1096
  // Replaces F12's real-repo bucket B apps/pwa scope check and its whole-repo export partition check.
@@ -1132,7 +1251,8 @@ export function readIsolatedRun(run: CommandResult, label: string, file: string)
1132
1251
  }
1133
1252
 
1134
1253
  class StalledPodShards extends Error {
1135
- constructor(readonly stalledShards: NonNullable<GateResult['stalledShards']>, readonly partialSummary: NonNullable<GateResult['partialSummary']>, readonly abortedShards = 0) {
1254
+ constructor(readonly stalledShards: NonNullable<GateResult['stalledShards']>, readonly partialSummary: NonNullable<GateResult['partialSummary']>,
1255
+ readonly partialFailures: SweepFailures, readonly preservedShardCount: number, readonly abortedShards = 0) {
1136
1256
  const unmeasured = stalledShards.filter((item) => item.stall);
1137
1257
  const exitCode = stalledShards.filter((item) => item.reason === 'exit-code');
1138
1258
  super(`pod sweep incomplete: stalled shards ${stalledShards.map(({ shard }) => shard).join(', ')}`
@@ -1142,6 +1262,23 @@ class StalledPodShards extends Error {
1142
1262
  }
1143
1263
  }
1144
1264
 
1265
+ /** Read completed shard failures without letting one malformed shard hide the stalled-shard verdict. */
1266
+ export function partialFailuresOf(runs: readonly (CommandResult & { files: number })[]): SweepFailures {
1267
+ return runs.reduce<SweepFailures>((sum, run) => {
1268
+ try {
1269
+ const parsed = failuresOf({ rc: run.rc, output: run.output, validatedShards: true }, `pod partial shard ${run.files}`);
1270
+ sum.failures.push(...parsed.failures);
1271
+ sum.errors.push(...parsed.errors);
1272
+ } catch (error) {
1273
+ debug.log('release-loop.gate', 'pod-partial-failures-unreadable', {
1274
+ files: run.files,
1275
+ reason: error instanceof Error ? error.message : String(error),
1276
+ }, { level: 'warn' });
1277
+ }
1278
+ return sum;
1279
+ }, { failures: [], errors: [] });
1280
+ }
1281
+
1145
1282
  class WarmRecheckIncomplete extends Error {
1146
1283
  constructor(message: string, readonly file: string, readonly shadow: WarmShadowObservation) { super(message); }
1147
1284
  }
@@ -1225,9 +1362,9 @@ export const gateAdmissionWaitText = (reading: string | null) => `메모리 여
1225
1362
  * the shard runs again on the others — one dead host no longer stops the gate. One-member pools keep the old behaviour.
1226
1363
  */
1227
1364
  export const GATE_MEMBER_UNREADABLE_SECONDS_DEFAULT = 600;
1228
- export function defaultGateMemberProbe(member: PodPoolMember): boolean {
1229
- if (!checkPodPool([member], defaultKubectl).ok) return false;
1230
- return !member.sshHost || defaultRemoteRun(member.sshHost, 'true').status === 0;
1365
+ export function defaultGateMemberProbe(member: PodPoolMember, tools: GateTools = {}): boolean {
1366
+ if (!checkPodPool([member], tools.kubectl ?? defaultKubectl).ok) return false;
1367
+ return !member.sshHost || (tools.remoteRun ?? defaultRemoteRun)(member.sshHost, 'true').status === 0;
1231
1368
  }
1232
1369
 
1233
1370
  export function poolGateAdmission(pool: PodPoolScheduler): GateAdmission {
@@ -1261,8 +1398,27 @@ export async function admitGateShard(admission: GateAdmission | undefined, o: {
1261
1398
  }
1262
1399
  }
1263
1400
 
1264
- export function createGateRunner(repo: string, remote?: string, commandOverride?: GateRunner['command'], podCommand: (options: RunPodCommandOptions) => Promise<PodCommandResult> = runPodCommand, poolOverride?: PodPoolScheduler, podLogTail?: (job: PodCommandResult) => string): GateRunner {
1401
+ /**
1402
+ * The real `ssh` transport behind {@link GateRunner.command} on a remote host. A scenario replaces it through
1403
+ * {@link GateTools.ssh} so the gate flow can run without any remote call.
1404
+ */
1405
+ export function defaultSshCommand(host: string, script: string, limitMs?: number): { rc: number; output: string; timedOut?: boolean } {
1406
+ // Start in /tmp, not the caller checkout. macOS resolves that symlink to /private/tmp before the child sees it;
1407
+ // the remote command still `cd`s to the quoted path, so either spelling is the same directory.
1408
+ const run = spawnSync('ssh', [host, script],
1409
+ { cwd: '/tmp', encoding: 'utf8', maxBuffer: 128 * 1024 * 1024, ...(limitMs ? { timeout: limitMs + 60_000, killSignal: 'SIGKILL' as const } : {}) });
1410
+ const timedOut = !!limitMs && (run.status === 124 || run.status === 137 || (run.error as NodeJS.ErrnoException | undefined)?.code === 'ETIMEDOUT');
1411
+ return { rc: run.status ?? 2, output: `${run.stdout ?? ''}\n${run.stderr ?? ''}${run.error ? `\n${run.error}` : ''}`, ...(timedOut ? { timedOut } : {}) };
1412
+ }
1413
+
1414
+ export function createGateRunner(repo: string, remote?: string, commandOverride?: GateRunner['command'], podCommand: (options: RunPodCommandOptions) => Promise<PodCommandResult> = runPodCommand, poolOverride?: PodPoolScheduler, podLogTail?: (job: PodCommandResult) => string, tools: GateTools = {}): GateRunner {
1265
1415
  let remoteMirror: string | undefined;
1416
+ // GATE-FLOW-SIM: the one place the real outside world is named — an injected seam replaces it, absent = today's tools.
1417
+ const clock = tools.now ?? Date.now;
1418
+ const kubectl = tools.kubectl ?? defaultKubectl;
1419
+ const ssh = tools.ssh ?? defaultSshCommand;
1420
+ const remoteRun = tools.remoteRun ?? defaultRemoteRun;
1421
+ const runPod = tools.podCommand ?? podCommand;
1266
1422
  // GATE-HOSTMIRROR-EXPLICIT: resolved once per runner and handed to every podCommand (shard · retry · recheck).
1267
1423
  let gateMirror: GateHostMirror | undefined;
1268
1424
  const gateHostMirror = (pod: NonNullable<GateOptions['pod']>): { hostMirror?: string } => {
@@ -1283,15 +1439,21 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1283
1439
  // GNU timeout signals its own process group: -s KILL would kill timeout too (ssh 255), so TERM first then KILL after 10s (124 / 137).
1284
1440
  // No coreutils timeout on the remote → run unlimited there and rely on the local ssh deadline.
1285
1441
  const limitPrefix = limitMs ? `$(command -v timeout >/dev/null 2>&1 && echo 'timeout -k 10 ${Math.ceil(limitMs / 1000)}') ` : '';
1286
- // Start in /tmp, not the caller checkout. macOS resolves that symlink to /private/tmp before the child sees it;
1287
- // the remote command still `cd`s to the quoted path, so either spelling is the same directory.
1288
- const run = spawnSync('ssh', [remote, `PATH=$HOME/.bun/bin:/opt/homebrew/bin:$PATH; export PATH; cd ${quote(cwd)} && ${limitPrefix}${[cmd, ...args].map(quote).join(' ')}`],
1289
- { cwd: '/tmp', encoding: 'utf8', maxBuffer: 128 * 1024 * 1024, ...(limitMs ? { timeout: limitMs + 60_000, killSignal: 'SIGKILL' as const } : {}) });
1290
- const timedOut = !!limitMs && (run.status === 124 || run.status === 137 || (run.error as NodeJS.ErrnoException | undefined)?.code === 'ETIMEDOUT');
1291
- return { rc: run.status ?? 2, output: `${run.stdout ?? ''}\n${run.stderr ?? ''}${run.error ? `\n${run.error}` : ''}`, ...(timedOut ? { timedOut } : {}) };
1442
+ return ssh(remote, `PATH=$HOME/.bun/bin:/opt/homebrew/bin:$PATH; export PATH; cd ${quote(cwd)} && ${limitPrefix}${[cmd, ...args].map(quote).join(' ')}`, limitMs);
1292
1443
  }
1293
1444
  : localCommand);
1445
+ const rulesGateBaseFor = async (commit: string, files: readonly string[]): Promise<string | undefined> => {
1446
+ if (!files.includes('test/rules-contract.test.ts')) return undefined;
1447
+ // The gate mirror only receives pinned commit refs, so its Pod checkout has no origin/main.
1448
+ // Resolve the same merge-base in the source repository and pass the exact commit to the Pod.
1449
+ const resolved = await localCommand('git', ['merge-base', commit, 'origin/main'], repo);
1450
+ if (resolved.rc !== 0) throw new Error(`rules gate base unavailable for ${commit.slice(0, 12)}: ${tail40(resolved.output)}`);
1451
+ const base = resolved.output.trim();
1452
+ if (!/^[0-9a-f]{40}$/.test(base)) throw new Error(`rules gate base invalid for ${commit.slice(0, 12)}: ${base}`);
1453
+ return base;
1454
+ };
1294
1455
  const podSweep = async (tree: string, logDir: string | undefined, pod: NonNullable<GateOptions['pod']>, only?: readonly string[]): Promise<CommandResult> => {
1456
+ const sweepId = randomUUID();
1295
1457
  const shardCount = pod.shards ?? 24;
1296
1458
  const isolationConcurrency = pod.isolationConcurrency ?? 4;
1297
1459
  // GATE-TIMEOUT-HEAL ①: the configured value is the floor; each shard's own deadline is shardDeadlineSeconds(planned).
@@ -1303,7 +1465,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1303
1465
  check(head, 'cut tree HEAD');
1304
1466
  const commit = head.output.trim();
1305
1467
  if (!/^[0-9a-f]{40}$/.test(commit)) throw new Error('invalid cut tree HEAD');
1306
- const selectionStart = Date.now();
1468
+ const selectionStart = clock();
1307
1469
  const listed = await command('git', ['ls-files', '*.test.*'], tree);
1308
1470
  check(listed, 'git ls-files tests');
1309
1471
  const allFiles = listed.output.split(/\r?\n/).filter((file) => /\.test\.(?:tsx?|jsx?|mts|cts)$/.test(file)).sort();
@@ -1313,6 +1475,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1313
1475
  const missing = only?.filter((file) => !allFiles.includes(file)) ?? [];
1314
1476
  if (missing.length) throw new Error(`partial plan file missing at this commit: ${missing.slice(0, 5).join(', ')}`);
1315
1477
  const files = onlySet ? allFiles.filter((file) => onlySet.has(file)) : allFiles;
1478
+ const rulesBaseSha = await rulesGateBaseFor(commit, files);
1316
1479
  if (files.some((file) => file.startsWith('-') || file.startsWith('/') || file.startsWith('./')
1317
1480
  || file.split('/').some((part) => !part || part === '.' || part === '..') || /[\r\n]/.test(file))) throw new Error('unsafe pod test path');
1318
1481
  if (new Set(files).size !== files.length) throw new Error('duplicate pod test path');
@@ -1332,12 +1495,14 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1332
1495
  // The TD1 measurements predate the fixture-only gate; do not isolate those five files at their old 2–13 GB peaks.
1333
1496
  const fixtureOnly = new Set([...GATE_NIGHTLY_AUDITS, 'test/guardian/dispatch-surface-contract.test.ts', 'test/user-config-mcp.test.ts']);
1334
1497
  const heavy = new Set(assignable.filter((file) => !fixtureOnly.has(file) && (memory.get(file) ?? 0) >= POD_HEAVY_FILE_MB));
1335
- const { shards: lightShards, spawnFiles: spawnLane, spawnShardCount } = planGateSpawnSeparatedShards(assignable.filter((file) => !heavy.has(file)), durations, shardCount);
1336
- const shards = [
1337
- ...lightShards,
1338
- ...[...heavy].map((file) => ({ files: [file], plannedSeconds: durations.get(file) ?? 0 })),
1339
- ];
1340
- if (spawnLane.length) debug.log('release-loop.gate', 'pod-spawn-lane', { files: spawnLane, shards: spawnShardCount });
1498
+ // GATE-RISK-LANE — the failure history this sweep already keeps (`gateHistory().watchList`) puts its watched files in
1499
+ // the first shard; heavy files stay solo and spawn files keep their own lane, so the rest of the plan is unchanged.
1500
+ const watchList = pod.riskWatch ? readGateRiskWatchList(pod.riskWatch) : [];
1501
+ const riskPlan = planGateRiskLaneShards({ assignable, durations, shardCount, heavy, watchList });
1502
+ const shards = riskPlan.shards;
1503
+ if (riskPlan.spawnFiles.length) debug.log('release-loop.gate', 'pod-spawn-lane', { files: riskPlan.spawnFiles, shards: riskPlan.spawnShardCount });
1504
+ if (riskPlan.riskFiles.length) debug.log('release-loop.gate', 'pod-risk-lane-plan', { files: riskPlan.riskFiles, shard: 0,
1505
+ plannedSeconds: riskPlan.riskPlannedSeconds, shards: shards.length, watch: watchList.length });
1341
1506
  logPlanWalltime(planWall, shards);
1342
1507
  if (heavy.size) debug.log('release-loop.gate', 'pod-heavy-alone', { files: [...heavy], thresholdMb: POD_HEAVY_FILE_MB, memoryLimit: POD_ISOLATION_MEMORY_LIMIT });
1343
1508
  const known = assignable.filter((file) => durations.has(file)).length;
@@ -1346,14 +1511,14 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1346
1511
  maxPlannedSeconds: Math.max(...shards.map((shard) => shard.plannedSeconds)),
1347
1512
  source: durations.size ? pod.durationSource : 'none',
1348
1513
  });
1349
- reportSelectionTimed(Date.now() - selectionStart, assignable.length, pod.onSelectionTimed);
1514
+ reportSelectionTimed(clock() - selectionStart, assignable.length, pod.onSelectionTimed);
1350
1515
  // GATE-SPEED A3①: shard Pod CPU (`release.loop.gatePodCpu` · default request 1 / limit 4) — shards × request must fit the pool.
1351
1516
  const cpu = resolvePodCpu(pod.cpu);
1352
1517
  // Only evidence-backed opt-in files reach this batch; every other file retains its own Pod below.
1353
1518
  const batchWidth = Math.max(1, Math.min(4, Math.floor(podCpuMillis(cpu.limit)! / 1000)));
1354
1519
  debug.log('release-loop.gate', 'shard-resources', { shards: shards.length, cpuRequest: cpu.request, cpuLimit: cpu.limit, cpuSource: pod.cpu ? 'config' : 'default' });
1355
1520
  debug.log('release-loop.gate', 'isolation-batch-width', { batchWidth, cpuLimit: cpu.limit });
1356
- const ready = poolOverride ? undefined : checkPodPool(parsePodPool(pod.pool), defaultKubectl);
1521
+ const ready = poolOverride ? undefined : checkPodPool(parsePodPool(pod.pool), kubectl);
1357
1522
  if (ready && !ready.ok) throw new Error('pod command: 풀의 노드가 하나도 준비되지 않았다');
1358
1523
  const poolScheduler = poolOverride ?? new PodPoolScheduler(ready!.ready);
1359
1524
  const configCache = typeof pod.bunCache === 'string' ? pod.bunCache.trim() : '';
@@ -1369,7 +1534,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1369
1534
  const admissionWaitSeconds = pod.admissionWaitSeconds ?? GATE_ADMISSION_WAIT_SECONDS_DEFAULT;
1370
1535
  if (!Number.isSafeInteger(admissionWaitSeconds) || admissionWaitSeconds < 0) throw new Error('invalid pod sweep options');
1371
1536
  // An injected pool (tests) runs without admission unless `pod.admission` is passed; the gate's own pool asks it.
1372
- const gateAdmission = pod.admission ?? (poolOverride ? undefined : poolGateAdmission(poolScheduler));
1537
+ const gateAdmission = pod.admission ?? tools.admission ?? (poolOverride ? undefined : poolGateAdmission(poolScheduler));
1373
1538
  debug.log('release-loop.gate', 'pod-admission', { waitSeconds: admissionWaitSeconds, source: pod.admissionWaitSeconds !== undefined ? 'config' : 'default', enabled: !!gateAdmission && admissionWaitSeconds > 0 });
1374
1539
  // GATE-POD-STALL-FAILFAST: a shard with no test output within the stall window is stopped and recorded unmeasured.
1375
1540
  const stallSeconds = pod.stallSeconds ?? GATE_POD_STALL_SECONDS_DEFAULT;
@@ -1378,14 +1543,14 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1378
1543
  // An injected pool (tests) runs without the watch unless `pod.liveLog` is passed — never a real kubectl from a test.
1379
1544
  const liveLog = pod.liveLog ?? (poolOverride ? undefined : (job: { name: string; context?: string }) => {
1380
1545
  if (!job.context) return null;
1381
- const logs = defaultKubectl(['--context', job.context, '-n', 'elanous-test', 'logs', `job/${job.name}`, '-c', 'child', '--tail=400']);
1546
+ const logs = kubectl(['--context', job.context, '-n', 'elanous-test', 'logs', `job/${job.name}`, '-c', 'child', '--tail=400']);
1382
1547
  return logs.status === 0 ? logs.stdout : null;
1383
1548
  });
1384
1549
  const stopJob = pod.stopJob ?? ((job: { name: string; context?: string }) => {
1385
1550
  // Not `delete job`: runPodCommand would poll a missing Job forever. A 1 s deadline makes the Job fail now and
1386
1551
  // runPodCommand returns through its normal path (logs · exit code · Secret cleanup).
1387
1552
  if (!job.context) return false;
1388
- const patched = defaultKubectl(['--context', job.context, '-n', 'elanous-test', 'patch', 'job', job.name, '--type=merge', '-p', JSON.stringify({ spec: { activeDeadlineSeconds: 1 } })]);
1553
+ const patched = kubectl(['--context', job.context, '-n', 'elanous-test', 'patch', 'job', job.name, '--type=merge', '-p', JSON.stringify({ spec: { activeDeadlineSeconds: 1 } })]);
1389
1554
  if (patched.status !== 0) debug.log('release-loop.gate', 'pod-shard-stall-stop-failed', { job: job.name, error: patched.stderr.trim().slice(0, 300) }, { level: 'warn' });
1390
1555
  return patched.status === 0;
1391
1556
  });
@@ -1396,7 +1561,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1396
1561
  if (!Number.isFinite(midRunStallSeconds) || midRunStallSeconds < 0) throw new Error('invalid pod sweep options');
1397
1562
  const abortTests = pod.abortTests ?? (poolOverride ? undefined : (job: { name: string; context?: string }, reason: string) => {
1398
1563
  if (!job.context) return false;
1399
- const ran = defaultKubectl(['--context', job.context, '-n', 'elanous-test', 'exec', `job/${job.name}`, '-c', 'child', '--', 'bash', '-c', gateShardAbortScript(reason)]);
1564
+ const ran = kubectl(['--context', job.context, '-n', 'elanous-test', 'exec', `job/${job.name}`, '-c', 'child', '--', 'bash', '-c', gateShardAbortScript(reason)]);
1400
1565
  if (ran.status !== 0) debug.log('release-loop.gate', 'shard-abort-failed', { job: job.name, rc: ran.status, out: `${ran.stdout}${ran.stderr}`.trim().slice(0, 300) }, { level: 'warn' });
1401
1566
  return ran.status === 0;
1402
1567
  });
@@ -1408,7 +1573,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1408
1573
  const downMembers = new Set<string>();
1409
1574
  let tailPlacedHost: string | undefined;
1410
1575
  const multiHost = poolScheduler.members.length > 1;
1411
- const probeMember = pod.probeMember ?? (poolOverride ? undefined : defaultGateMemberProbe);
1576
+ const probeMember = pod.probeMember ?? (poolOverride ? undefined : (member: PodPoolMember) => defaultGateMemberProbe(member, { kubectl, remoteRun }));
1412
1577
  const memberUnreadableSeconds = pod.memberUnreadableSeconds ?? GATE_MEMBER_UNREADABLE_SECONDS_DEFAULT;
1413
1578
  if (!Number.isFinite(memberUnreadableSeconds) || memberUnreadableSeconds < 0) throw new Error('invalid pod sweep options');
1414
1579
  debug.log('release-loop.gate', 'pod-roster', { members: poolScheduler.members.map((m) => ({ context: m.context, host: m.sshHost ?? null, capacity: m.capacity })),
@@ -1429,11 +1594,38 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1429
1594
  const jobLogTail = (job: PodCommandResult): string => {
1430
1595
  if (podLogTail) return podLogTail(job);
1431
1596
  for (const member of poolScheduler.members.filter((m) => !downMembers.has(m.context))) {
1432
- const logs = defaultKubectl(['--context', member.context, '-n', 'elanous-test', 'logs', `job/${job.job}`, '-c', 'child', '--tail=200']);
1597
+ const logs = kubectl(['--context', member.context, '-n', 'elanous-test', 'logs', `job/${job.job}`, '-c', 'child', '--tail=200']);
1433
1598
  if (logs.status === 0 && logs.stdout) return logs.stdout;
1434
1599
  }
1435
1600
  return '';
1436
1601
  };
1602
+ const failureEvidence = pod.failureEvidence ?? ((job: { name: string; context?: string }) => {
1603
+ if (!job.context) return { error: '수거 실패 · Job 배치 context 없음' };
1604
+ const prefix = ['--context', job.context, '--request-timeout=5s', '-n', 'elanous-test'];
1605
+ const errors: string[] = [];
1606
+ let termination: { reason?: string; message?: string; exitCode?: number; finishedAt?: string } | undefined;
1607
+ let podName: string | undefined;
1608
+ let latestEndedAt = -Infinity;
1609
+ const status = (pod.failureKubectl ?? kubectl)([...prefix, 'get', 'pods', '-l', `job-name=${job.name}`, '-o', 'json']);
1610
+ if (status.status !== 0) errors.push(`Pod 상태: ${status.stderr.trim() || `kubectl rc=${status.status}`}`);
1611
+ else try {
1612
+ const pods = JSON.parse(status.stdout) as { items?: Array<{ metadata?: { name?: string; creationTimestamp?: string }; status?: { containerStatuses?: Array<{ name?: string; state?: { terminated?: typeof termination } }> } }> };
1613
+ for (const item of pods.items ?? []) {
1614
+ const child = item.status?.containerStatuses?.find((container) => container.name === 'child');
1615
+ const ended = child?.state?.terminated;
1616
+ if (!ended || !item.metadata?.name) continue;
1617
+ const endedAt = Date.parse(ended.finishedAt ?? item.metadata.creationTimestamp ?? '');
1618
+ const rank = Number.isFinite(endedAt) ? endedAt : -Infinity;
1619
+ if (!termination || rank > latestEndedAt) { podName = item.metadata.name; termination = ended; latestEndedAt = rank; }
1620
+ }
1621
+ if (!termination) errors.push('Pod 상태: child 종료 사유 없음');
1622
+ } catch (error) { errors.push(`Pod 상태 JSON: ${String(error)}`); }
1623
+ const logs = podName ? (pod.failureKubectl ?? kubectl)([...prefix, 'logs', `pod/${podName}`, '-c', 'child', '--tail=100']) : undefined;
1624
+ if (!logs) errors.push('로그: 종료된 Pod 이름 없음');
1625
+ else if (logs.status !== 0) errors.push(`로그: ${logs.stderr.trim() || `kubectl rc=${logs.status}`}`);
1626
+ return { ...(podName ? { podName } : {}), ...(termination ? { termination } : {}), ...(logs?.status === 0 ? { logTail: logs.stdout } : {}),
1627
+ ...(errors.length ? { error: `수거 실패 · ${errors.join(' · ')}` } : {}) };
1628
+ });
1437
1629
  type ShardRun = { output: string; rc: number; pass: number; fail: number; errors: number; ran: number; files: number; passedIds: string[] };
1438
1630
  type ShardReason = NonNullable<GateResult['stalledShards']>[number]['reason'];
1439
1631
  // 깊이 끝 OOM 조각은 «조각 전체»를 파일 하나씩 격리한다 — 상한 40 이면 나머지가 못 잰 채 남았다(09-30 ① 7번 조각 110 파일).
@@ -1502,17 +1694,18 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1502
1694
  const shardDeadline = shardDeadlineSeconds(batchIsolation ? Math.max(...paths.map((file) => estimated([file]))) * Math.ceil(paths.length / batchWidth) : estimated(paths), deadlineSeconds);
1503
1695
  const parts = batchIsolation ? paths.map((file) => [file]) : shardParts(paths, (file) => estimated([file]));
1504
1696
  const memoryLimit = batchIsolation || paths.length === 1 && (depth >= 1 || heavy.has(paths[0]!)) ? POD_ISOLATION_MEMORY_LIMIT : undefined;
1505
- const shellOptions = { parts, cdpPatterns, softSeconds: softDeadlineSeconds(shardDeadline), cachePrefix, installSlots, ...(batchIsolation ? { parallelParts: batchWidth } : {}) };
1697
+ const shellOptions = { parts, cdpPatterns, softSeconds: softDeadlineSeconds(shardDeadline), cachePrefix, installSlots, ...(rulesBaseSha ? { rulesBaseSha } : {}), ...(batchIsolation ? { parallelParts: batchWidth } : {}) };
1506
1698
  const keyShell = gateShardShell(shellOptions);
1507
1699
  // The stall beat only reports; runKey keeps hashing the shell without it, so earlier results stay reusable.
1508
1700
  const runKey = createHash('sha256').update(JSON.stringify({ pool: pod.pool, podShell: keyShell, memoryLimit: memoryLimit ?? null, deadlineSeconds: shardDeadline, bunCache: bunCache ?? null })).digest('hex');
1509
- let start = Date.now();
1701
+ let start = clock();
1510
1702
  let admitted: GateAdmissionResult | undefined;
1511
1703
  let output: string | undefined;
1512
1704
  const phaseTimes: Record<string, string> = {};
1513
1705
  const stamp = (phase: string) => { phaseTimes[phase] = new Date().toISOString(); };
1514
1706
  let podStages: Record<string, number> | undefined;
1515
1707
  let podLifecycle: Record<string, string> | undefined;
1708
+ let imageDigest: string | null = null;
1516
1709
  let podJobName: string | undefined;
1517
1710
  let batchArtifactsComplete = true;
1518
1711
  let junit: string | undefined;
@@ -1522,6 +1715,13 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1522
1715
  let lastFile: string | undefined;
1523
1716
  let jobFailed = false;
1524
1717
  let jobError = '';
1718
+ let failureCapture: ReturnType<NonNullable<NonNullable<GateOptions['pod']>['failureEvidence']>> | undefined;
1719
+ let jobNameForCapture: string | undefined;
1720
+ let jobContextForCapture: string | undefined;
1721
+ const captureFailure = () => {
1722
+ try { failureCapture = failureEvidence({ name: jobNameForCapture ?? 'unplaced', context: jobContextForCapture }); }
1723
+ catch (error) { failureCapture = { error: `수거 실패 · ${String(error)}` }; }
1724
+ };
1525
1725
  /** GATE-MULTI-HOST — the member this shard's Job was placed on (recorded in the shard json / log / board). */
1526
1726
  let placedOn: string | undefined;
1527
1727
  let timedOut = false;
@@ -1570,8 +1770,9 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1570
1770
  if (admitted.outcome !== 'off') debug.log('release-loop.gate', 'shard-admission', { shard, branch, outcome: admitted.outcome, waitedMs: admitted.waitedMs, ...(admitted.reason ? { reason: admitted.reason } : {}) },
1571
1771
  admitted.outcome === 'granted' ? undefined : { level: 'warn' });
1572
1772
  // The admission wait is not the shard's run time: deadline/timeout judgment and the measured duration start here.
1573
- start = Date.now();
1773
+ start = clock();
1574
1774
  const jobName = `gate-${randomUUID()}`;
1775
+ jobNameForCapture = jobName;
1575
1776
  // The member's context is known once the Job is placed; the live log is read (and the Job stopped) there.
1576
1777
  let jobContext: string | undefined;
1577
1778
  const liveJob = () => ({ name: jobName, ...(jobContext ? { context: jobContext } : {}) });
@@ -1592,9 +1793,16 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1592
1793
  },
1593
1794
  } : {}) }) : undefined;
1594
1795
  const placed = admitted.release;
1595
- const job = await podCommand({
1796
+ const job = await runPod({
1797
+ provenanceGate: true,
1798
+ provenance: { actorKind: process.env.ELANOUS_SEAT ? 'seat' : 'loop', actor: process.env.ELANOUS_SEAT || 'release-loop',
1799
+ character: 'gate-shard-runner', launchHost: hostname(), entrance: process.env.ELANOUS_RELEASE_ENTRANCE?.trim() || 'release-run',
1800
+ kind: 'gate-shard', run: process.env.ELANOUS_RUN_ID || sweepId, phase: 'release-gate', requestedAt: new Date().toISOString(),
1801
+ graphNode: 'gate', version: pod.shardsFile?.version, commit, shard: `${shard + 1}-of-${shards.length}`,
1802
+ reason: `release gate shard ${shard + 1}/${shards.length}`, pool: pod.pool, cpuReq: cpu.request,
1803
+ memReq: memoryLimit ?? POD_ISOLATION_MEMORY_LIMIT, warmReused: false },
1596
1804
  pool: pod.pool, poolScheduler: trackedPool(shard, depth > 0 || branch !== '', (member) => {
1597
- stamp('poolPlaced'); jobContext = member.context; placedOn = member.context; placed();
1805
+ stamp('poolPlaced'); jobContext = member.context; jobContextForCapture = member.context; placedOn = member.context; placed();
1598
1806
  if (branch === '-tail') tailPlacedHost = member.context;
1599
1807
  }, skipMembers), clone: true, source: { kind: 'commit', sha: commit }, deadlineSeconds: shardDeadline,
1600
1808
  ...gateHostMirror(pod),
@@ -1609,13 +1817,20 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1609
1817
  }).finally(() => watch?.stop());
1610
1818
  stamp('jobReturned');
1611
1819
  podJobName = job.job;
1820
+ // poolOverride is the injected scheduler used by isolated tests; it has no authenticated kubectl
1821
+ // context. Those sweeps intentionally cannot mint a valid baseline receipt (missing observed digest).
1822
+ // Production pool sweeps always use this Kubernetes lookup; lookup failure is receipt-invalid only.
1612
1823
  if (!poolOverride && placedOn) {
1613
1824
  try {
1614
- const got = defaultKubectl(['--context', placedOn, '-n', 'elanous-test', 'get', 'pods', '-l', `job-name=${job.job}`, '-o', 'json']);
1615
- if (got.status === 0) podLifecycle = readGatePodLifecycle(got.stdout);
1825
+ const got = kubectl(['--context', placedOn, '-n', 'elanous-test', 'get', 'pods', '-l', `job-name=${job.job}`, '-o', 'json']);
1826
+ if (got.status === 0) {
1827
+ podLifecycle = readGatePodLifecycle(got.stdout);
1828
+ imageDigest = actualGatePodImageDigest(got.stdout);
1829
+ }
1616
1830
  } catch { /* timings never change the verdict */ }
1617
1831
  }
1618
1832
  jobExitCode = job.exitCode;
1833
+ if (job.exitCode !== 0) captureFailure();
1619
1834
  batchArtifactDir = job.artifactsDir;
1620
1835
  const read = readShardArtifacts(job.artifactsDir, parts);
1621
1836
  batchArtifactsComplete = !batchIsolation || parts.every((_, index) =>
@@ -1640,7 +1855,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1640
1855
  if (abort?.taken) abortedShards++;
1641
1856
  if (stalledAt && !finished.length && rc === undefined) stall = { seconds: stallSeconds, sinceCheckoutMs: stalledAt.sinceCheckoutMs };
1642
1857
  // A Job the cluster cut at its own deadline leaves no outbox at all: past the deadline with nothing is a timeout too.
1643
- if (!timedOut && output === undefined && job.exitCode !== 0 && Date.now() - start >= shardDeadline * 1000) timedOut = true;
1858
+ if (!timedOut && output === undefined && job.exitCode !== 0 && clock() - start >= shardDeadline * 1000) timedOut = true;
1644
1859
  lastFile = output === undefined && unfinishedLog === undefined ? undefined : lastStartedTestFile(`${output ?? ''}\n${unfinishedLog ?? ''}`, paths);
1645
1860
  if (!lastFile && (job.exitCode !== 0 || rc === undefined || (rc !== 0 && rc !== 1))) {
1646
1861
  try { lastFile = lastStartedTestFile(jobLogTail(job).split(/\r?\n/).slice(-200).join('\n'), paths); }
@@ -1649,10 +1864,11 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1649
1864
  } catch (error) {
1650
1865
  jobFailed = true;
1651
1866
  jobError = error instanceof Error ? error.message : String(error);
1867
+ if (!failureCapture) captureFailure();
1652
1868
  }
1653
1869
  // A launch that failed before placement still gives its admission back (release is idempotent).
1654
1870
  admitted?.release();
1655
- const durationMs = Date.now() - start;
1871
+ const durationMs = clock() - start;
1656
1872
  const fileSeconds = junit === undefined ? undefined : Object.fromEntries(readJunitFileSeconds(junit));
1657
1873
  const testSeconds = fileSeconds === undefined ? undefined : Object.values(fileSeconds).reduce((sum, seconds) => sum + seconds, 0);
1658
1874
  const testWallSeconds = batchIsolation
@@ -1683,13 +1899,25 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1683
1899
  if (unfinishedLog !== undefined) writeFileSync(unfinishedDestination, unfinishedLog, { mode: 0o600 });
1684
1900
  else rmSync(unfinishedDestination, { force: true });
1685
1901
  writeFileSync(join(logDir, `pod-${shard}${branch}.json`), JSON.stringify({ durationMs, rc: rc ?? null,
1686
- ...(stall ? { reason: 'stall', finishedFiles: 0, stallSeconds: stall.seconds, sinceCheckoutMs: stall.sinceCheckoutMs } : timedOut ? { reason: 'timeout', finishedFiles: finished.length } : {}), files: paths, plannedSeconds: estimated(paths), deadlineSeconds: shardDeadline, shardCount: shards.length, commit, runKey, attemptId, ...(parentAttempt ? { parentAttempt } : {}), jobExitCode: jobExitCode ?? null, jobFailed, ...(placedOn ? { host: placedOn } : {}),
1902
+ ...(stall ? { reason: 'stall', finishedFiles: 0, stallSeconds: stall.seconds, sinceCheckoutMs: stall.sinceCheckoutMs } : timedOut ? { reason: 'timeout', finishedFiles: finished.length } : {}), files: paths, plannedSeconds: estimated(paths), deadlineSeconds: shardDeadline, shardCount: shards.length, sweepId, commit, runKey, attemptId, ...(parentAttempt ? { parentAttempt } : {}), jobExitCode: jobExitCode ?? null, jobFailed, ...(placedOn ? { host: placedOn } : {}),
1687
1903
  ...(Object.keys(phaseTimes).length ? { phaseTimes } : {}), ...(podStages ? { podStages } : {}),
1688
1904
  ...(podLifecycle && Object.keys(podLifecycle).length ? { podLifecycle } : {}),
1905
+ ...(failureCapture ? { failureCapture: {
1906
+ ...(failureCapture.podName ? { podName: failureCapture.podName } : {}),
1907
+ ...(failureCapture.termination ? { termination: {
1908
+ ...failureCapture.termination,
1909
+ ...(failureCapture.termination.reason ? { reason: redactSecretText(failureCapture.termination.reason) } : {}),
1910
+ ...(failureCapture.termination.message ? { message: redactSecretText(failureCapture.termination.message) } : {}),
1911
+ } } : {}),
1912
+ ...(failureCapture.logTail !== undefined ? { logTail: redactedGateLogTail(failureCapture.logTail) } : {}),
1913
+ ...(failureCapture.error ? { error: redactSecretText(failureCapture.error) } : {}),
1914
+ } } : {}),
1915
+ ...(imageDigest ? { imageDigest } : {}),
1689
1916
  ...(fileSeconds !== undefined ? { fileSeconds, testSeconds } : {}), ...(testWallSeconds !== undefined ? { testWallSeconds } : {}),
1690
1917
  ...(podOverheadSeconds !== undefined ? { podOverheadSeconds } : {}),
1691
1918
  ...(unfinishedLogBytes !== undefined ? { unfinishedLogBytes } : {}),
1692
- ...(admitted && admitted.outcome !== 'off' ? { admission: admitted.outcome, admissionWaitMs: admitted.waitedMs } : {}),
1919
+ ...(admitted && admitted.outcome !== 'off' ? { admission: admitted.outcome, admissionWaitMs: admitted.waitedMs,
1920
+ ...(admitted.reason ? { admissionReason: admitted.reason } : {}) } : {}),
1693
1921
  ...(abort ? { abort: { reason: `abort: ${abort.reason}`, at: abort.at, by: abort.by, sinceLastOutputMs: abort.sinceLastOutputMs, taken: abort.taken } } : {}),
1694
1922
  stallWatch: shardStallWatchRecord(stallSeconds, !!liveLog, stallWatchState) }) + '\n', { mode: 0o600 });
1695
1923
  if (paths.length === 1 && (rc === 0 || rc === 1) && !jobFailed && jobExitCode === 0
@@ -1904,9 +2132,13 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1904
2132
  ran: Number(ran![1]), files: Number(ran![2]), passedIds: junit ? junitPassedIds(junit) : [] }], stalled: [] };
1905
2133
  };
1906
2134
  // 모든 조각이 끝난 뒤 판정한다 — 한 조각의 예외로 먼저 돌아가면 다른 Pod 가 도는 채로 정리가 시작된다(#22002 리뷰 R3).
2135
+ // GATE-RISK-LANE — the risk shard's own wall time (planned vs measured in its log line); it never changes a verdict.
2136
+ const shardDurations: number[] = [];
1907
2137
  const settled = await Promise.allSettled(shards.map(async (item, shard) => {
2138
+ const shardStartedAt = clock();
1908
2139
  try {
1909
2140
  const done = await runShard(item.files, shard);
2141
+ shardDurations[shard] = clock() - shardStartedAt;
1910
2142
  const state = !done.stalled.length ? 'done' as const : done.stalled.some((stall) => stall.reason === 'timeout') ? 'timeout' as const : 'failed' as const;
1911
2143
  markShard(shard, { state, endedAt: new Date().toISOString() }, ['waitReason']);
1912
2144
  return done;
@@ -1918,6 +2150,16 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1918
2150
  const rejected = settled.find((result): result is PromiseRejectedResult => result.status === 'rejected');
1919
2151
  if (rejected) throw rejected.reason;
1920
2152
  const results = settled.flatMap((result) => result.status === 'fulfilled' ? [result.value] : []);
2153
+ // GATE-RISK-LANE — planned vs measured for the shard that ran first, with its own result. Its Pods are ordinary
2154
+ // shard Pods: their runs join `measured` below, so the sweep's summary and every later judgment are unchanged.
2155
+ let riskLane: GateRiskLaneRecord | undefined;
2156
+ if (riskPlan.riskFiles.length) {
2157
+ const riskRuns = results[0]?.runs ?? [];
2158
+ riskLane = { files: [...riskPlan.riskFiles], plannedSeconds: riskPlan.riskPlannedSeconds,
2159
+ durationMs: shardDurations[0] ?? 0, rc: riskRuns.length ? (riskRuns.some((run) => run.fail + run.errors > 0) ? 1 : 0) : null };
2160
+ debug.log('release-loop.gate', 'pod-risk-lane', { files: riskLane.files, shard: 0, plannedSeconds: riskLane.plannedSeconds,
2161
+ durationMs: riskLane.durationMs, rc: riskLane.rc, ...(results[0]?.stalled[0] ? { reason: results[0].stalled[0]!.reason } : {}) });
2162
+ }
1921
2163
  let stalledShards = results.flatMap((result) => result.stalled);
1922
2164
  const measured = results.flatMap((result) => result.runs);
1923
2165
  // GATE-STALLED-TAIL-REMEASURE: after the sweep, give only a one-file no-output leaf one fresh attempt on a different
@@ -1963,13 +2205,19 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1963
2205
  stallSeconds, measuredFiles: total.files }, { level: 'warn' });
1964
2206
  // An unmeasured (stalled) shard is never handed to the host re-run as an environment stall: the gate fails now.
1965
2207
  if (abortedShards) debug.log('release-loop.gate', 'pod-sweep-aborted', { shards: abortedShards, midRunStallSeconds }, { level: 'warn' });
1966
- if (stalledShards.some((item) => item.reason !== 'no-output' || item.files.length !== 1 || item.stall)) throw new StalledPodShards(stalledShards, total, abortedShards);
2208
+ if (stalledShards.some((item) => item.reason !== 'no-output' || item.files.length !== 1 || item.stall)) {
2209
+ const measuredFailures = partialFailuresOf(measured);
2210
+ throw new StalledPodShards(stalledShards, total, measuredFailures,
2211
+ results.filter((item) => item.stalled.length === 0).length, abortedShards);
2212
+ }
1967
2213
  if (total.ran === 0 && total.errors === 0 && !stalledShards.length) throw new Error('sweep incomplete: no tests ran');
1968
2214
  const outputs = measured.map((run) => run.output.replace(/(?:^|\n)\s*\d+ (?:pass|fail|errors?)\s*(?=\n|$)/g, '\n').replace(/Ran \d+ tests? across \d+ files?\.?/g, ''));
1969
2215
  return { rc: total.fail + total.errors ? 1 : 0, output: outputs.join('\n') + `\n${total.pass} pass\n${total.fail} fail\n${total.errors} errors\nRan ${total.ran} tests across ${total.files} files.\n`, validatedShards: true,
2216
+ podSweep: { id: sweepId, shards: shards.length, files: assignable },
1970
2217
  passedIds: [...new Set(measured.flatMap((run) => run.passedIds))].sort(),
1971
2218
  stalledEnv: stalledShards.flatMap((item) => item.files.map((file) => ({ file, reason: 'no-output' as const }))),
1972
- ...(abortedShards ? { abortedShards } : {}) };
2219
+ ...(abortedShards ? { abortedShards } : {}),
2220
+ ...(riskLane ? { riskLane } : {}) };
1973
2221
  };
1974
2222
  const runPodRecheckBatch = async (files: readonly string[], commit: string, pod: NonNullable<GateOptions['pod']>, timeoutMs: number, relaxed = false): Promise<Map<string, CommandResult>> => {
1975
2223
  const warmShadowScope = pod.warmShadowScope ?? { requested: false };
@@ -1992,6 +2240,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1992
2240
  || file.split('/').some((part) => part === '.' || part === '..')))
1993
2241
  throw new Error('unsafe pod recheck files');
1994
2242
  if (!sha.test(commit)) throw new Error('invalid pod recheck commit');
2243
+ const rulesBaseSha = await rulesGateBaseFor(commit, files);
1995
2244
  const installSlots = pod.installSlots ?? POD_INSTALL_SLOTS_DEFAULT;
1996
2245
  if (!Number.isSafeInteger(installSlots) || installSlots < 0) throw new Error('invalid pod recheck install slots');
1997
2246
  const bunCache = pod.bunCache?.trim() || process.env[POD_BUN_CACHE_HOST_PATH]?.trim();
@@ -2000,18 +2249,51 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2000
2249
  const deadlineSeconds = partLimitSeconds * Math.ceil(files.length / 2) + POD_SHARD_EMIT_RESERVE_SECONDS + 60;
2001
2250
  const shell = gateShardShell({ parts: files.map((file) => [file]), cdpPatterns: [], softSeconds: softDeadlineSeconds(deadlineSeconds),
2002
2251
  cachePrefix: bunCache ? podBunCacheVolume(bunCache).shellPrefix : '', installSlots, parallelParts: files.length > 1 ? 2 : 1,
2003
- partLimitSeconds, sourceBinding: { cutSha: commit, attemptId }, ...(relaxed ? { testTimeoutMs: GATE_TIMEOUT_RECHECK_MS } : {}) });
2004
- const job = await podCommand({ pool: pod.pool, ...(poolOverride ? { poolScheduler: poolOverride } : {}), clone: true,
2005
- source: { kind: 'commit', sha: commit }, deadlineSeconds, ...(bunCache ? { bunCache } : {}), ...gateHostMirror(pod),
2252
+ partLimitSeconds, sourceBinding: { cutSha: commit, attemptId }, ...(rulesBaseSha ? { rulesBaseSha } : {}), ...(relaxed ? { testTimeoutMs: GATE_TIMEOUT_RECHECK_MS } : {}) });
2253
+ const warmEligible = files.length === 1 && pod.warmEligible === true && !!pod.warmReplay && !!pod.warmQuarantine && !!pod.warmFinalize
2254
+ && pod.warmCutSha === commit;
2255
+ const warmRequest = warmEligible && pod.warmProducerEnabled && pod.warmReplayOwner !== false ? { protocol: 1 as const, runId: randomUUID(), attemptId, cutSha: commit,
2256
+ ...(pod.warmBaselineSha ? { baselineSha: pod.warmBaselineSha } : {}), deadlineAt: Date.now() + timeoutMs + 60_000,
2257
+ files, envProfile: 'gate-v1' } : undefined;
2258
+ const replay = warmRequest ? await pod.warmReplay!(warmRequest) : undefined;
2259
+ const leased = replay?.command;
2260
+ let closed = false;
2261
+ const quarantine = async (reason: string) => {
2262
+ if (!leased || closed) return;
2263
+ await pod.warmQuarantine!(leased, reason);
2264
+ closed = true;
2265
+ };
2266
+ let producer: RunPodCommandOptions['warmProducer'];
2267
+ if (leased && replay?.transport.status === 'observed' && replay.transport.reason === 'complete') {
2268
+ if (warmCommandMatchesRequest(leased, warmRequest!) && leased.podName && leased.podUid
2269
+ && leased.podContext && leased.image?.endsWith(`@${leased.key.imageDigest}`)) {
2270
+ producer = { pod: leased.podName, podUid: leased.podUid, context: leased.podContext, image: leased.image,
2271
+ commit, files: ['package.json', 'bun.lock'] };
2272
+ } else await quarantine('gate-producer-identity');
2273
+ }
2274
+ let job: PodCommandResult;
2275
+ try {
2276
+ job = await runPod({ pool: pod.pool, ...(poolOverride ? { poolScheduler: poolOverride } : {}), clone: true,
2277
+ provenanceGate: true,
2278
+ provenance: { actorKind: process.env.ELANOUS_SEAT ? 'seat' : 'loop', actor: process.env.ELANOUS_SEAT || 'release-loop',
2279
+ character: 'gate-shard-runner', launchHost: hostname(), entrance: 'release-recheck', kind: 'gate-recheck',
2280
+ run: process.env.ELANOUS_RUN_ID || attemptId, phase: 'release-gate', requestedAt: new Date().toISOString(),
2281
+ graphNode: 'gate-triage', commit, reason: `release gate recheck ${files.length} files`, pool: pod.pool,
2282
+ memReq: POD_ISOLATION_MEMORY_LIMIT, warmReused: !!producer },
2283
+ source: { kind: 'commit', sha: commit }, deadlineSeconds, ...(producer ? { warmProducer: producer, image: producer.image, imageCommit: commit } : {}),
2284
+ ...(bunCache ? { bunCache } : {}), ...gateHostMirror(pod),
2006
2285
  ...(installSlots > 0 ? { installSlots: POD_INSTALL_SLOTS_HOST_PATH } : {}), ...(pod.cpu ? { cpu: pod.cpu } : {}),
2007
2286
  memoryLimit: POD_ISOLATION_MEMORY_LIMIT, name: `gate-recheck-${randomUUID()}`, command: ['bash', '-lc', shell] });
2008
- const artifact = (name: string) => {
2009
- const path = join(job.artifactsDir, name);
2010
- return existsSync(path) ? readFileSync(path, 'utf8') : undefined;
2011
- };
2287
+ } catch (error) {
2288
+ await quarantine('gate-job-failed');
2289
+ throw error;
2290
+ }
2291
+ try {
2292
+ const artifact = (name: string) => {
2293
+ const path = join(job.artifactsDir, name);
2294
+ return existsSync(path) ? readFileSync(path, 'utf8') : undefined;
2295
+ };
2012
2296
  const installed = artifact('install.rc')?.trim() === '0';
2013
- const warmEligible = files.length === 1 && pod.warmEligible === true && !!pod.warmReplay && !!pod.warmQuarantine && !!pod.warmFinalize
2014
- && pod.warmCutSha === commit;
2015
2297
  const incomplete = (message: string, file: string): Error => warmEligible
2016
2298
  ? new WarmRecheckIncomplete(message, file, { files: [file], attemptId, status: 'unmeasured', reason: 'reference-unmeasured', quarantined: 'no' })
2017
2299
  : new Error(message);
@@ -2048,7 +2330,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2048
2330
  }
2049
2331
  debug.log('release-loop.gate', 'pod-isolated-recheck', { file, commit, job: job.job, rc: rc ?? 124, relaxed });
2050
2332
  }
2051
- if (warmEligible && pod.warmReplay && pod.warmQuarantine && pod.warmFinalize) {
2333
+ if (warmEligible && !pod.warmProducerEnabled && pod.warmReplay && pod.warmQuarantine && pod.warmFinalize) {
2052
2334
  shadowDeadline = Date.now() + shadowTimeoutMs;
2053
2335
  const first = results.get(files[0]!)!;
2054
2336
  const reference = first.warmReference;
@@ -2124,11 +2406,26 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2124
2406
  debug.log('release-loop.gate', 'warm-shadow-recheck', first.warmShadow);
2125
2407
  }
2126
2408
  }
2409
+ if (producer && leased) {
2410
+ const reference = results.get(files[0]!)?.warmReference;
2411
+ const decision = job.exitCode === 0 && reference && !timedOut
2412
+ ? assessWarmWorkerShadow(leased, [reference], replay!.transport.reply) : null;
2413
+ if (decision?.reason !== 'match') await quarantine(decision?.reason ?? 'reference-unmeasured');
2414
+ else {
2415
+ await pod.warmFinalize!(leased);
2416
+ closed = true;
2417
+ }
2418
+ }
2127
2419
  return results;
2420
+ } catch (error) {
2421
+ await quarantine('gate-job-incomplete');
2422
+ throw error;
2423
+ }
2128
2424
  };
2129
2425
  return {
2130
2426
  command,
2131
2427
  localCommand: commandOverride && !remote ? commandOverride : localCommand,
2428
+ now: clock,
2132
2429
  async recheckPod(file, commit, pod, timeoutMs, relaxed = false) {
2133
2430
  return (await runPodRecheckBatch([file], commit, pod, timeoutMs, relaxed)).get(file)!;
2134
2431
  },
@@ -2295,8 +2592,8 @@ export function saveBaselineFileCache(ledger: string, commit: string, rows: Base
2295
2592
  } finally { if (existsSync(temp)) rmSync(temp); }
2296
2593
  }
2297
2594
 
2298
- export async function judgeGate(opts: GateOptions, runner: GateRunner = createGateRunner(opts.repo ?? process.cwd(), opts.remote)): Promise<GateResult> {
2299
- const start = Date.now();
2595
+ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGateRunner(opts.repo ?? process.cwd(), opts.remote),
2596
+ imageProbe: typeof preflightGateImageDigest = preflightGateImageDigest): Promise<GateResult> {
2300
2597
  if (opts.pod?.warmReplay) opts = { ...opts, pod: { ...opts.pod,
2301
2598
  warmShadowScope: { requested: false, attempts: 0 } } };
2302
2599
  const explicitConfig = getElanousConfigDirOverride();
@@ -2307,6 +2604,10 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2307
2604
  debug.log('release.run', opts.forceFreeze ? 'freeze-forced' : 'frozen', { version: opts.version, reason: frozen.reason, until: frozen.until, node: 'gate' });
2308
2605
  if (!opts.forceFreeze) throw new Error(landingFreezeMessage(frozen));
2309
2606
  }
2607
+ // GATE-FLOW-SIM — the injected clock (absent = `Date.now`), read only after the freeze check: a frozen gate must not
2608
+ // touch its runner at all (a refusal is decided before any side effect).
2609
+ const clock = runner.now ?? Date.now;
2610
+ const start = clock();
2310
2611
  const ledger = explicitConfig ? root : (opts.ledgerRoot ?? releaseLedgerRoot());
2311
2612
  const roots = [...new Set([ledger, root])];
2312
2613
  const cachedBaseline = (version: string, commit: string) => {
@@ -2340,6 +2641,8 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2340
2641
  || (opts.baselineVersion && !versionPattern.test(opts.baselineVersion))
2341
2642
  || (opts.baselineCommit && !sha.test(opts.baselineCommit))) throw new Error('invalid commit or version');
2342
2643
  if (!opts.baselineVersion) throw new Error('previous release version required (--baseline-version)');
2644
+ if (opts.rehearsalBaseline && (opts.graphRunId || !opts.pod || !/-rc\.\d+$/.test(opts.version)))
2645
+ throw new Error('rehearsal baseline requires a direct Pod gate with an isolated rc version');
2343
2646
  // Observation only. Failure to plan cannot change the gate selector or verdict.
2344
2647
  try { radiusShadow = planGateRadiusShadow({ repo, ledgerRoot: ledger, version: opts.version,
2345
2648
  baselineVersion: opts.baselineVersion, forCommit: opts.commit, instanceRoot: root,
@@ -2388,12 +2691,31 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2388
2691
  for (const dir of [cutTree, join(cutTree, 'apps/pwa')]) check(await runner.command('bun', ['install'], dir), `bun install ${dir}`);
2389
2692
  // GATE-PARTIAL: a plan made by `release resume --from gate --partial` for exactly this commit re-runs only its files.
2390
2693
  // Its logs go to cut-partial/ — the full cut/ logs stay whole (the next release reads them as a baseline source).
2391
- const partial = readPartialPlan(roots, opts.version, opts.commit);
2392
- if (partial) debug.log('release-loop.gate', 'partial-regate', { version: opts.version, priorCommit: partial.priorCommit, commit: opts.commit, files: partial.files.length });
2694
+ const partialRead = readPartialPlanResult(roots, opts.version, opts.commit);
2695
+ const explicitPartial = partialRead.plan;
2696
+ const rehearsalStampPresent = ['hard-5h', 'hard-mid'].some((step) =>
2697
+ existsSync(join(root, 'release', opts.version, 'rehearsal', `${step}.json`)));
2698
+ const currentImage = !explicitPartial && opts.graphRunId && opts.pod && rehearsalStampPresent
2699
+ ? imageProbe(opts.pod.pool) : null;
2700
+ const radius = !explicitPartial && opts.graphRunId && opts.pod && rehearsalStampPresent
2701
+ ? planHardRehearsalRadius({ root, repo, version: opts.version, forCommit: opts.commit,
2702
+ graphPins: gateGraphPins(root, opts.graphRunId), envFingerprint: gateEnvFingerprint(process.env), imageDigest: currentImage })
2703
+ : null;
2704
+ if (radius) debug.log('release-loop.gate', 'radius-live-plan', { version: opts.version, reason: radius.reason,
2705
+ files: radius.plan?.files.length ?? null, rehearsalRunId: radius.rehearsalRunId ?? null });
2706
+ let partial = explicitPartial ?? radius?.plan ?? null;
2707
+ debug.log('release-loop.gate', partial ? 'partial-regate' : 'partial-fallback', {
2708
+ version: opts.version, priorCommit: partial?.priorCommit ?? null, commit: opts.commit,
2709
+ planKind: partial?.kind ?? 'full', remeasureFileCount: partial?.files.length ?? 0,
2710
+ preservedShardCount: partial?.kind === 'recheck' && 'preservedShardCount' in partial
2711
+ && typeof partial.preservedShardCount === 'number' ? partial.preservedShardCount : 0,
2712
+ fallbackReason: partial ? undefined : partialRead.fallbackReason,
2713
+ });
2393
2714
  if (partial?.kind === 'carry') {
2715
+ const carryPriorCommit = partial.priorCommit;
2394
2716
  // The plan alone is not evidence: the old cut and its verdict must still be complete and clean at this SHA.
2395
- if (!verifyDocsOnlyCarry(repo, partial.priorCommit, opts.commit))
2396
- throw new Error(`carry plan diff is not docs-only: ${partial.priorCommit}..${opts.commit}`);
2717
+ if (!verifyDocsOnlyCarry(repo, carryPriorCommit, opts.commit))
2718
+ throw new Error(`carry plan diff is not docs-only: ${carryPriorCommit}..${opts.commit}`);
2397
2719
  const trusted = roots.some((location) => {
2398
2720
  const dir = join(location, 'release', opts.version);
2399
2721
  const failuresPath = join(dir, 'gate-failures.json');
@@ -2401,11 +2723,11 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2401
2723
  if (!existsSync(failuresPath) || !existsSync(verdictPath)) return false;
2402
2724
  const failures = JSON.parse(readFileSync(failuresPath, 'utf8')) as { commit?: unknown; failures?: unknown; errors?: unknown };
2403
2725
  const verdict = JSON.parse(readFileSync(verdictPath, 'utf8')) as { commit?: unknown; outcome?: unknown };
2404
- return failures.commit === partial.priorCommit && Array.isArray(failures.failures) && failures.failures.length === 0
2726
+ return failures.commit === carryPriorCommit && Array.isArray(failures.failures) && failures.failures.length === 0
2405
2727
  && (failures.errors === undefined || (Array.isArray(failures.errors) && failures.errors.length === 0))
2406
- && verdict.commit === partial.priorCommit && verdict.outcome === 'ok';
2728
+ && verdict.commit === carryPriorCommit && verdict.outcome === 'ok';
2407
2729
  });
2408
- if (!trusted) throw new Error(`carry plan has no complete clean gate at ${partial.priorCommit}`);
2730
+ if (!trusted) throw new Error(`carry plan has no complete clean gate at ${carryPriorCommit}`);
2409
2731
  }
2410
2732
  // GATE-SKIP-KNOWN-RETRY — the ids the verdict will not count as introduced (the recorded baseline ⊕ env-known), read
2411
2733
  // before the sweep so a shard failing only with them skips its isolation retries. No recorded baseline = no skip.
@@ -2420,12 +2742,30 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2420
2742
  } catch (error) { debug.log('release-loop.gate', 'known-failures-unavailable', { error: String(error) }); }
2421
2743
  }
2422
2744
  const durationSource = join(ledger, 'release', opts.baselineVersion, 'gate-logs', 'cut');
2423
- const cutRun = partial?.kind === 'carry' ? undefined : await runner.sweep(cutTree, join(root, 'release', opts.version, 'gate-logs', partial ? 'cut-partial' : 'cut'),
2745
+ const runCut = (selected: typeof partial) => selected?.kind === 'carry' ? undefined : runner.sweep(cutTree,
2746
+ join(root, 'release', opts.version, 'gate-logs', selected ? 'cut-partial' : 'cut'),
2424
2747
  opts.pod ? { ...opts.pod, durationSource, ...(knownFailures.length ? { knownFailures } : {}), onSelectionTimed: ({ ms }) => { result.selectionMs = ms; },
2748
+ // GATE-RISK-LANE — the watch list is read from this gate's own ledgers (the instance root ⊕ the machine ledger);
2749
+ // the cut sweep puts those files first, the baseline sweep keeps its plan.
2750
+ riskWatch: { instanceRoots: roots, ledgerRoots: roots },
2425
2751
  // GATE-LIVE-OBS: the release ledger's shards.json (UX #24782) — never the run tree's universe; an explicit
2426
2752
  // --ledger-root (tests) keeps it in that ledger.
2427
2753
  shardsFile: { path: opts.ledgerRoot ? gateShardsPath(opts.version, opts.ledgerRoot) : gateShardsPath(opts.version), version: opts.version } } : undefined,
2428
- partial?.files);
2754
+ selected?.files);
2755
+ let cutRun = await runCut(partial);
2756
+ const radiusSelectionMatched = partial?.files && cutRun?.podSweep?.files
2757
+ && JSON.stringify([...partial.files].sort()) === JSON.stringify([...cutRun.podSweep.files].sort());
2758
+ if (radius?.plan && (!radiusSelectionMatched
2759
+ || !partialGateImagesMatch(root, opts.version, currentImage!, opts.commit, cutRun?.podSweep))) {
2760
+ debug.log('release-loop.gate', 'radius-live-image-drift', { version: opts.version,
2761
+ rehearsalRunId: radius.rehearsalRunId, action: 'full-sweep' }, { level: 'warn' });
2762
+ partial = null;
2763
+ cutRun = await runCut(null);
2764
+ }
2765
+ if (radius?.plan && partial === radius.plan) debug.log('release-loop.gate', 'baseline-consumed', {
2766
+ version: opts.version, baselineCommit: radius.plan.priorCommit, commit: opts.commit,
2767
+ selectedFiles: radius.plan.files.length, rehearsalRunId: radius.rehearsalRunId,
2768
+ });
2429
2769
  cutSweepRc = cutRun?.rc ?? null;
2430
2770
  cutSweepUsable = !!cutRun && (cutRun.rc === 0 || cutRun.rc === 1) && !cutRun.timedOut
2431
2771
  && !cutRun.abortedShards && !cutRun.stalledEnv?.length;
@@ -2621,7 +2961,7 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2621
2961
  debug.log('release-loop.gate', 'isolated-recheck-plan', { files: recheckFiles.length, window: recheckLimit,
2622
2962
  concurrency: opts.pod && runner.recheckPod ? recheckConcurrency : 1, executor: opts.pod && runner.recheckPod ? 'pod' : 'host',
2623
2963
  remote: opts.remote ?? null, podBatchFiles: opts.pod && runner.recheckPod && runner.recheckPodBatch ? GATE_RECHECK_POD_BATCH_FILES : 1 });
2624
- const recheckStartedAt = Date.now();
2964
+ const recheckStartedAt = clock();
2625
2965
  const recordWarmShadow = (shadow?: WarmShadowObservation) => {
2626
2966
  if (shadow && !result.warmShadow?.some((entry) => entry.attemptId === shadow.attemptId))
2627
2967
  (result.warmShadow ??= []).push(shadow);
@@ -2784,6 +3124,11 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2784
3124
  result.introduced.sort();
2785
3125
  preexistingIds.sort();
2786
3126
  result.loadFlaky?.sort();
3127
+ // GATE-RISK-LANE — did the shard that ran first carry this gate's new failures? Observation only: the outcome above
3128
+ // is the existing aggregation, and the counts (introduced · preexisting) are the same ones the verdict writes.
3129
+ if (cutRun?.riskLane) debug.log('release-loop.gate', 'risk-lane-introduced', { files: cutRun.riskLane.files,
3130
+ plannedSeconds: cutRun.riskLane.plannedSeconds, durationMs: cutRun.riskLane.durationMs, rc: cutRun.riskLane.rc,
3131
+ introduced: result.introduced.filter((id) => cutRun!.riskLane!.files.includes(fileOf(id))).length });
2787
3132
  result.preexisting = preexistingIds.length;
2788
3133
  result.baselineCache = cacheStats;
2789
3134
  debug.log('release.gate', 'baseline-cache', { hits: cacheStats.hits, misses: cacheStats.misses, commit: baseSha });
@@ -2816,6 +3161,8 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2816
3161
  if (error instanceof StalledPodShards) {
2817
3162
  result.stalledShards = error.stalledShards;
2818
3163
  result.partialSummary = error.partialSummary;
3164
+ result.partialFailures = error.partialFailures;
3165
+ result.preservedShardCount = error.preservedShardCount;
2819
3166
  if (error.abortedShards) result.abortedShards = error.abortedShards;
2820
3167
  }
2821
3168
  } finally {
@@ -2834,7 +3181,7 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2834
3181
  } catch (error) { result.outcome = 'error'; result.error = `cleanup: ${String(error)}`; }
2835
3182
  }
2836
3183
  if (opts.remote) runner.remoteMirror = undefined;
2837
- result.durationMs = Date.now() - start;
3184
+ result.durationMs = clock() - start;
2838
3185
  }
2839
3186
  if (cutFailures && !result.stalledShards?.length && !result.stalledEnv.length && !result.unmeasuredRecheckFiles?.length) {
2840
3187
  try {
@@ -2866,6 +3213,13 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2866
3213
  };
2867
3214
  const observed = { ...(result.selectionMs !== undefined ? { selectionMs: result.selectionMs } : {}),
2868
3215
  ...(result.warmShadow?.length ? { warmShadow: result.warmShadow } : {}) };
3216
+ // Receipt is advisory for a later radius gate. Missing identity makes reuse invalid, never this gate's verdict.
3217
+ const baselineReceipt = gateBaselineReceipt({ root, version: opts.version, commit: opts.commit,
3218
+ graphPins: gateGraphPins(root, opts.graphRunId) ?? (opts.rehearsalBaseline ? gateSourcePins(repo, opts.commit) : null),
3219
+ envFingerprint: gateEnvFingerprint(process.env),
3220
+ fullSweep: result.outcome === 'ok' && cutSweepUsable && !result.partial && !!cutFailures
3221
+ && !result.rcNonzeroNoFailures.length && !result.stalledShards?.length && !result.stalledEnv.length
3222
+ && !result.unmeasuredRecheckFiles?.length && !result.unmeasuredErrors?.length });
2869
3223
  try {
2870
3224
  for (const location of roots) {
2871
3225
  const path = verdictFile(location, opts.version);
@@ -2878,7 +3232,7 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2878
3232
  try {
2879
3233
  const warmHistory = readWarmVerdictHistory(path) ?? [];
2880
3234
  writeFileSync(temp, JSON.stringify({ version: opts.version, commit: opts.commit,
2881
- outcome: result.outcome, ...ids, ...observed,
3235
+ outcome: result.outcome, ...ids, ...observed, baselineReceipt,
2882
3236
  ...(opts.pod?.warmReplay ? { baselineCommit, warmHistory } : {}) }, null, 2) + '\n', { mode: 0o600 });
2883
3237
  chmodSync(temp, 0o600);
2884
3238
  renameSync(temp, path);
@@ -2894,7 +3248,7 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2894
3248
  try {
2895
3249
  const warmHistory = readWarmVerdictHistory(path) ?? [];
2896
3250
  writeFileSync(temp, JSON.stringify({ version: opts.version, commit: opts.commit,
2897
- outcome: result.outcome, ...ids, ...observed,
3251
+ outcome: result.outcome, ...ids, ...observed, baselineReceipt,
2898
3252
  ...(opts.pod?.warmReplay ? { baselineCommit, warmHistory } : {}) }, null, 2) + '\n', { mode: 0o600 });
2899
3253
  chmodSync(temp, 0o600);
2900
3254
  renameSync(temp, path);
@@ -3023,6 +3377,7 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
3023
3377
  const arg = args[i];
3024
3378
  if (arg === '--help') return 'help';
3025
3379
  if (arg === '--json') continue;
3380
+ if (arg === '--rehearsal-baseline') { opts.rehearsalBaseline = true; continue; }
3026
3381
  // 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).
3027
3382
  if (arg === '--fresh-shards') { freshShards = true; continue; }
3028
3383
  if (arg === '--rehearsal-tui') { opts.rehearsalTui = true; continue; }
@@ -3085,7 +3440,7 @@ export async function runGateNode(argv: string[], env: NodeJS.ProcessEnv, runner
3085
3440
  debug.log('release-loop.gate', 'executor', { version: opts.version, ...placement, requested: opts.executor ?? null });
3086
3441
  }
3087
3442
  if (opts === 'help') {
3088
- 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] [--executor <pod|host>] [--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).');
3443
+ 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] [--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.');
3089
3444
  return 0;
3090
3445
  }
3091
3446
  version = opts.version;
@@ -3098,7 +3453,7 @@ export async function runGateNode(argv: string[], env: NodeJS.ProcessEnv, runner
3098
3453
  throw new Error('release gate host reservation requires graph runId and pinned state root');
3099
3454
  const releaseHosts = context && hosts.length
3100
3455
  ? reserveReleaseGateHosts(reservationRoot!, context.runId!, hosts) : () => undefined;
3101
- try { result = await judge(opts, runner); }
3456
+ try { result = await judge(context?.runId ? { ...opts, graphRunId: context.runId } : opts, runner); }
3102
3457
  finally { releaseHosts(); }
3103
3458
  } catch (error) {
3104
3459
  result = { outcome: 'error', commit: '', introduced: [], preexisting: 0, fixed: 0, knownEnv: 0, knownEnvCleared: [], stalledEnv: [], rcNonzeroNoFailures: [], durationMs: Date.now() - start, error: String(error) };