elanous 0.2.35 → 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 (686) 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/{d06f9160.a3d9062f95de8ad7.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/{4346.8601952e73bf5803.js → 1201.afbfc5b1c5e5cbe4.js} +1 -1
  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/{3103.0df8da390948bb84.js → 2500.cbc1257b774aae52.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/{7644.17e37ba6f1d4226e.js → 2683.c9773a0c589b031b.js} +3 -3
  20. package/apps/pwa/out/_next/static/chunks/2709.8e1889034255fc5f.js +24 -0
  21. package/apps/pwa/out/_next/static/chunks/{4718-17d2af636796a330.js → 275-02ea4c2a33980422.js} +3 -3
  22. package/apps/pwa/out/_next/static/chunks/2812ca6a.3b9f4a7a08885744.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/284.ac77c0db55bfd3a8.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/{8714.ff0b7ce3ec3e3ae2.js → 2941.3c3546f96bc6efa7.js} +1 -1
  25. package/apps/pwa/out/_next/static/chunks/2976.b000698a3c6512dc.js +24 -0
  26. package/apps/pwa/out/_next/static/chunks/{9214.63a510d39178e997.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/3136.30aaaa5fa03662a3.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/{3677-4215f826f6aa68e5.js → 3148-98efd4869d660a87.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/3155.2587a55196ecb394.js +226 -0
  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/3324.a09703033abcbd2f.js +34 -0
  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/3538.a445e50bb359d068.js +32 -0
  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/{9512.5a76207a730e0491.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/{8557-e7898769499cd56f.js → 3757-180308bc61021b05.js} +1 -1
  44. package/apps/pwa/out/_next/static/chunks/379.ee262e37586fe1ae.js +74 -0
  45. package/apps/pwa/out/_next/static/chunks/{359-2250789a2d3499e7.js → 3840-5825d56d46976a92.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/3969-81054381eaec4c4c.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/{da5176c6.6773b6c5994e1d99.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/{9952.5daf5f94063a2b3e.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/{938.66ad1ef0e5abae1c.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/5085-bce4904931767ca6.js +1 -0
  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/{6236.e18cced2d5ba6afb.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/{3408.21d1570602f4b6c0.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/5711.957b095705c12560.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/{1530.0e612369b6fd7ea0.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/599.c39c571b6733f2a2.js +41 -0
  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/6925.562d532110879886.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/{7385.50a308a477bdfc21.js → 6943.c6b24124853a47b2.js} +1 -1
  87. package/apps/pwa/out/_next/static/chunks/{630.fe5a46428d63b013.js → 727.7deb87749b0a343a.js} +1 -1
  88. package/apps/pwa/out/_next/static/chunks/{2018.a741297bce759490.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/{8444.4c26b379ca2b9596.js → 771.c3cdb6c1f7f47c02.js} +17 -17
  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/{3589.5444a06bcf6e775c.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/{b2f6ba4d.fb56fdbbf798df33.js → 873d6c12.09bd499f57171543.js} +1 -1
  101. package/apps/pwa/out/_next/static/chunks/{5101.e32282718f99ed6f.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/{5637.281e5dec4ea6ecf1.js → 914.8abefd48d579547b.js} +2 -2
  105. package/apps/pwa/out/_next/static/chunks/9187-c87a2207d4ac5d46.js +1 -0
  106. package/apps/pwa/out/_next/static/chunks/{4705-194e64e47ca0fbdb.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/{8637.47f5b35afcae5e6b.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/{1674.2a65fd5668e1cc5f.js → 9789.60f723d18097ec30.js} +1 -1
  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-083747f1d4518f61.js +1 -0
  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/{0368f25d-ad84af92fefee68f.js → ccae1c4b-0cf414f5eff1b10d.js} +1 -1
  174. package/apps/pwa/out/_next/static/chunks/{cbe38a15.5a032598e39970c6.js → ceba0382.650712b56b85b582.js} +1 -1
  175. package/apps/pwa/out/_next/static/chunks/{b6705a4f-b9a9047de9eecfd0.js → da5ecc5c-b005ad8cac737d57.js} +1 -1
  176. package/apps/pwa/out/_next/static/chunks/{56d372f2.681ab4ec4c998fae.js → f9a61e4d.1fc2c1dbdd49fe46.js} +1 -1
  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/{T1mBQqmkwKDLGtyisDCOd → 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/docs/goal-context/05-authoring-lessons.jsonl +47 -0
  278. package/docs/goal-context/README.md +3 -0
  279. package/graphs/default-loop.yaml +6 -1
  280. package/graphs/implement-loop.yaml +24 -0
  281. package/graphs/release/recipes.yaml +6 -0
  282. package/graphs/release/release-loop.yaml +17 -4
  283. package/graphs/wizard/recipes.yaml +44 -0
  284. package/graphs/wizard/wizard-loop.yaml +44 -0
  285. package/graphs/work/research-report.yaml +2 -0
  286. package/package.json +1 -1
  287. package/scripts/ci-typecheck-changed.ts +139 -5
  288. package/scripts/coord-role-pack.ts +1 -1
  289. package/scripts/first10-gate.ts +416 -0
  290. package/scripts/fleet/wsl-node-setup.sh +432 -0
  291. package/scripts/release-loop/docs-node.ts +204 -8
  292. package/scripts/release-loop/gate-node.ts +249 -53
  293. package/scripts/release-loop/node-verdict.ts +2 -0
  294. package/scripts/release-loop/resume-cap.ts +14 -34
  295. package/scripts/release-loop/resume-release.ts +37 -11
  296. package/scripts/release-loop/traffic-node.ts +59 -0
  297. package/scripts/release-loop/traffic-transition.ts +159 -0
  298. package/scripts/rhythm/daily.ts +137 -3
  299. package/scripts/ux-sim.ts +53 -0
  300. package/skills/README.md +1 -0
  301. package/src/acp/core-turn-bridge.ts +48 -6
  302. package/src/agent/delegate-hint.ts +16 -0
  303. package/src/agent/runner.ts +82 -2
  304. package/src/agent/task-notification.ts +56 -10
  305. package/src/agent/types.ts +4 -0
  306. package/src/agent-substrate/pr-reviewer.ts +55 -3
  307. package/src/agent-substrate/review-intent.ts +29 -2
  308. package/src/agent-substrate/review-observation.ts +3 -0
  309. package/src/agent-substrate/self-review-cli.ts +168 -16
  310. package/src/autopilot/mission-codebase-gate.ts +141 -12
  311. package/src/autopilot/pr-manager.ts +25 -5
  312. package/src/boot/daemon-tools/self-implement.ts +1 -0
  313. package/src/budget/usage-outcomes.ts +8 -4
  314. package/src/capture/tui-self-report.ts +102 -0
  315. package/src/chat/goal-continue-verdict.ts +41 -0
  316. package/src/chat/index.ts +30 -4
  317. package/src/chat/internal-turn-origin.ts +1 -0
  318. package/src/chat/paste-fold.ts +86 -0
  319. package/src/chat/tool-render/block.ts +32 -0
  320. package/src/chat/tool-render/dispatch.ts +2 -20
  321. package/src/chat/tool-render/result-text.ts +98 -0
  322. package/src/chat/turn-typeahead.ts +70 -7
  323. package/src/cli/config-audit.ts +119 -0
  324. package/src/cli/doctor-cli.ts +22 -0
  325. package/src/cli/guardian-cli.ts +10 -0
  326. package/src/cli/herdr-cli.ts +34 -0
  327. package/src/cli/light-entry-route.ts +6 -3
  328. package/src/cli/log-event-names.ts +2 -0
  329. package/src/cli/make-cli.ts +247 -11
  330. package/src/cli/msg-cli.ts +117 -3
  331. package/src/cli/pr-cli.ts +29 -16
  332. package/src/cli/pr-land-base-read.ts +11 -2
  333. package/src/cli/pr-land-heavy-remote.ts +6 -3
  334. package/src/cli/pr-land-overlap-evidence.ts +23 -4
  335. package/src/cli/pr-land-recovery.ts +117 -1
  336. package/src/cli/pty-takeover-cli.ts +8 -7
  337. package/src/cli/release-cli.ts +21 -5
  338. package/src/cli/release-single-flight.ts +1 -1
  339. package/src/cli/tasks-card-show.ts +162 -0
  340. package/src/cli/tasks-cli.ts +574 -356
  341. package/src/cli/tasks-read-cli.ts +197 -0
  342. package/src/core-turn/run-core-turn.ts +4 -2
  343. package/src/core-turn/run-goal-loop.ts +73 -16
  344. package/src/core-turn/types.ts +6 -1
  345. package/src/dashboard/context/keys.ts +8 -31
  346. package/src/dashboard/index.ts +202 -49
  347. package/src/dashboard/input/agent-wake.ts +7 -1
  348. package/src/dashboard/input/chat-main-plain-turn-runtime.ts +146 -3
  349. package/src/dashboard/input/chat-main-slash-command.ts +4 -2
  350. package/src/dashboard/input/goal-continue.ts +210 -0
  351. package/src/dashboard/input/goal-interim.ts +2 -0
  352. package/src/dashboard/input/internal-turn.ts +2 -8
  353. package/src/dashboard/input/seat-autowake.ts +9 -7
  354. package/src/dashboard/input/turn-autoheal.ts +52 -0
  355. package/src/dashboard/seat-boot-preamble.ts +132 -5
  356. package/src/dashboard/slash-runtime/chat-persona.ts +65 -8
  357. package/src/dashboard/slash-runtime/dashboard-handlers.ts +3 -2
  358. package/src/dashboard/slash-runtime/model-choices.ts +15 -0
  359. package/src/dashboard/stale-install-check.ts +3 -99
  360. package/src/debug/batch-queue.ts +119 -0
  361. package/src/debug/tui-log-tag.ts +67 -0
  362. package/src/display/agent-surface.ts +7 -0
  363. package/src/domains/standalone-log-sink.ts +15 -2
  364. package/src/esc-abort-gate.ts +69 -0
  365. package/src/git-fs/worktree.ts +95 -1
  366. package/src/graph-runner/graph-cli.ts +18 -0
  367. package/src/graph-runner/runner.ts +20 -11
  368. package/src/graph-wizard/generate.ts +108 -4
  369. package/src/graph-wizard/steps.ts +15 -0
  370. package/src/grounding/proactive-ground.ts +86 -0
  371. package/src/harness/control-inbox.ts +49 -1
  372. package/src/harness/harness-cli-command.ts +51 -7
  373. package/src/harness/harness-incidents.ts +52 -1
  374. package/src/harness/pod-control-send.ts +33 -5
  375. package/src/harness/self-send-remote.ts +69 -0
  376. package/src/harness/self-send-routing-guard.ts +6 -0
  377. package/src/harness/self-send-target.ts +4 -3
  378. package/src/index.ts +327 -12
  379. package/src/input-core/context-key-runtime.ts +21 -0
  380. package/src/light-cli.ts +6 -1
  381. package/src/llm-wellformed.ts +70 -0
  382. package/src/llm.ts +35 -4
  383. package/src/loops/msg-wake.ts +35 -39
  384. package/src/loops/orchestrator/inbox.ts +59 -11
  385. package/src/loops/orchestrator/knowhow-pack.ts +364 -0
  386. package/src/loops/orchestrator/tick.ts +5 -1
  387. package/src/maturity/feature-maturity.ts +1 -1
  388. package/src/models/prompts.ts +17 -2
  389. package/src/msg/msg-post-office.ts +116 -0
  390. package/src/mss/logging/log-store.ts +79 -22
  391. package/src/nexus/api/decisions-route.ts +38 -18
  392. package/src/nexus/api/graph-wizard-api.ts +20 -2
  393. package/src/nexus/boot/register-mcp-clients.ts +263 -38
  394. package/src/nexus/onboarding/first-boot-wizard.ts +56 -8
  395. package/src/onboarding/first-run.ts +37 -6
  396. package/src/onboarding/web-first.ts +43 -11
  397. package/src/ops/overview-collect.ts +12 -6
  398. package/src/ops/overview.ts +3 -3
  399. package/src/persona/loader.ts +11 -0
  400. package/src/persona/types.ts +8 -0
  401. package/src/prompt-library/session-guidance.ts +14 -0
  402. package/src/provider-summary.ts +8 -0
  403. package/src/pty-shell/herdr-agent-report.ts +118 -9
  404. package/src/pty-shell/herdr-prompt.ts +266 -0
  405. package/src/pty-shell/pty-control-ipc.ts +151 -8
  406. package/src/pty-shell/pty-special-keys.ts +42 -2
  407. package/src/pty-shell/stale-install-check.ts +99 -0
  408. package/src/pty-shell/tui-run-state.ts +14 -2
  409. package/src/release-loop/auto-start.ts +20 -2
  410. package/src/release-loop/checklist.ts +76 -4
  411. package/src/release-loop/rehearsal.ts +36 -20
  412. package/src/repl/index.ts +19 -1
  413. package/src/seat-loop/knowhow-lint.ts +1 -1
  414. package/src/self-dev/decompose-proposal.ts +172 -20
  415. package/src/self-dev/decompose.ts +61 -0
  416. package/src/self-dev/dev-cli.ts +37 -3
  417. package/src/self-dev/dev-pipeline.ts +8 -2
  418. package/src/self-dev/draft-triage-rules.ts +31 -0
  419. package/src/self-dev/glm-credential-guard.ts +77 -0
  420. package/src/self-dev/graph-journey-nodes.ts +1 -0
  421. package/src/self-dev/graph-journey-route.ts +12 -2
  422. package/src/self-dev/orchestrate-cli.ts +68 -3
  423. package/src/self-dev/orchestrate.ts +116 -17
  424. package/src/self-dev/run-supervisor.ts +55 -2
  425. package/src/self-dev/run-types.ts +1 -1
  426. package/src/self-health/autoheal.ts +153 -0
  427. package/src/self-health/signatures.ts +6 -0
  428. package/src/self-implement/device-first.ts +116 -0
  429. package/src/self-implement/doctor-visit.ts +239 -1
  430. package/src/self-implement/evidence-command-budget.ts +80 -0
  431. package/src/self-implement/goal-author-cli.ts +1 -0
  432. package/src/self-implement/goal-author-decompose.ts +265 -4
  433. package/src/self-implement/goal-author.ts +33 -8
  434. package/src/self-implement/goal-elaborate.ts +176 -22
  435. package/src/self-implement/goal-refine.ts +3 -0
  436. package/src/self-implement/graph-variant.ts +40 -6
  437. package/src/self-implement/headless-elanous-driver.ts +19 -3
  438. package/src/self-implement/off-diff-evidence.ts +58 -6
  439. package/src/self-implement/orchestrator.ts +222 -21
  440. package/src/self-implement/pod-warm-pool-profile.ts +1 -0
  441. package/src/self-implement/pwa-import-graph.ts +1 -1
  442. package/src/self-implement/rework-policy.ts +30 -0
  443. package/src/self-implement/run-ledger.ts +108 -0
  444. package/src/self-implement/run-ttl.ts +38 -24
  445. package/src/self-implement/scope-adjacency.ts +3 -1
  446. package/src/self-implement/seams.ts +60 -4
  447. package/src/self-implement/wiring-check.ts +358 -0
  448. package/src/shell-env-bootstrap.ts +11 -4
  449. package/src/skills/runner.ts +4 -2
  450. package/src/skills/tools/agent.ts +290 -48
  451. package/src/skills/tools/schedule-wakeup.ts +23 -4
  452. package/src/skills/tools/shell.ts +61 -6
  453. package/src/skills/tools/terminal-modal-inject.ts +1 -2
  454. package/src/skills/tools/terminal-modal.ts +10 -8
  455. package/src/skills/url-router.ts +41 -1
  456. package/src/smart-wizard/run.ts +66 -0
  457. package/src/status/bar.ts +34 -6
  458. package/src/status/status-provider-info.ts +37 -6
  459. package/src/steward/rejudge-tick.ts +10 -2
  460. package/src/steward/ta-repair-event-collect.ts +17 -6
  461. package/src/task-agent/actions.ts +3 -1
  462. package/src/task-agent/land-queue.ts +21 -8
  463. package/src/task-agent/live-moves.ts +22 -6
  464. package/src/task-agent/mission.ts +30 -0
  465. package/src/task-agent/rejudge-on-head.ts +15 -0
  466. package/src/task-agent/repair-cycle.ts +138 -32
  467. package/src/task-agent/repair-next-move.ts +31 -3
  468. package/src/task-agent/repair-outcomes.ts +29 -0
  469. package/src/task-agent/repair-worker.ts +14 -4
  470. package/src/task-agent/shadow.ts +41 -3
  471. package/src/task-agent/task-card-prefix.ts +2 -0
  472. package/src/task-agent/task-hand.ts +477 -22
  473. package/src/task-agent/task-parent-role-llm.ts +36 -0
  474. package/src/task-agent/tier-split.ts +78 -9
  475. package/src/task-orchestrator/surfaces/pod-account-broker.ts +9 -0
  476. package/src/task-orchestrator/surfaces/pod-child-reuse.ts +146 -0
  477. package/src/task-orchestrator/surfaces/pod-command-job.ts +86 -5
  478. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +215 -3
  479. package/src/task-orchestrator/surfaces/pod-launch-history.ts +133 -0
  480. package/src/task-orchestrator/surfaces/pod-manifest-test-normalize.ts +11 -0
  481. package/src/task-orchestrator/surfaces/pod-pool.ts +76 -4
  482. package/src/task-orchestrator/surfaces/pod-provenance.ts +8 -2
  483. package/src/task-orchestrator/surfaces/pod-repair-commit.ts +18 -3
  484. package/src/task-orchestrator/surfaces/pod-si-warm-entry.ts +16 -0
  485. package/src/task-orchestrator/surfaces/pod-si-warm-host.ts +224 -0
  486. package/src/task-orchestrator/surfaces/pod-si-warm-protocol.ts +201 -0
  487. package/src/task-orchestrator/surfaces/pod-si-warm-provider.ts +444 -0
  488. package/src/task-orchestrator/surfaces/pod-si-warm-runner.ts +387 -0
  489. package/src/task-orchestrator/surfaces/pod-si-warm-transport.ts +157 -0
  490. package/src/task-orchestrator/surfaces/pod-warm-worker-protocol.ts +4 -0
  491. package/src/task-orchestrator/surfaces/pod-warm-worker-provider.ts +485 -36
  492. package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +3 -0
  493. package/src/task-orchestrator/surfaces/self-implement-pod.ts +396 -19
  494. package/src/task-orchestrator/surfaces/self-implement.ts +42 -2
  495. package/src/tool-runtime/index.ts +15 -2
  496. package/src/tool-runtime/mcp-proxy-bootstrap.ts +29 -0
  497. package/src/tty-foreground.ts +155 -0
  498. package/src/tui/chat/goal-interim.ts +173 -0
  499. package/src/tui/chat/log-view.ts +205 -18
  500. package/src/tui/chat/self-health-status.ts +232 -0
  501. package/src/tui/entry.ts +370 -81
  502. package/src/tui/input/composer.ts +8 -1
  503. package/src/tui/slash/compact-command.ts +7 -0
  504. package/src/tui/slash/compact.ts +1 -7
  505. package/src/tui/slash/resume.ts +75 -0
  506. package/src/tui-client/local-chat-turn.ts +45 -2
  507. package/src/tui-client/next-dashboard-runtime.ts +8 -0
  508. package/src/tui-client/next-loop-command.ts +70 -0
  509. package/src/tui-client/next-wakeup.ts +191 -0
  510. package/src/tui.ts +37 -0
  511. package/src/ui/modal-adapter.ts +2 -2
  512. package/src/ux-sim/classic-tui-seat-scenarios.ts +516 -0
  513. package/src/ux-sim/cli-config-audit-device.ts +121 -0
  514. package/src/ux-sim/dashboard-event-loop-stall-monitor-device.ts +238 -0
  515. package/src/ux-sim/event-monkey.ts +3 -2
  516. package/src/ux-sim/fake-bin.ts +45 -0
  517. package/src/ux-sim/fake-herdr.ts +238 -0
  518. package/src/ux-sim/fake-llm.ts +111 -6
  519. package/src/ux-sim/fault-glm-no-credential.ts +116 -0
  520. package/src/ux-sim/fault-nextmove-dispatch.ts +102 -1
  521. package/src/ux-sim/fault-scope-escape-lift.ts +33 -6
  522. package/src/ux-sim/fault-tui-400-autoheal.ts +297 -0
  523. package/src/ux-sim/goal-device.ts +619 -0
  524. package/src/ux-sim/in-process-cli.ts +22 -5
  525. package/src/ux-sim/mock-authoring-first-run-child.ts +35 -0
  526. package/src/ux-sim/mock-authoring-first-run.ts +97 -0
  527. package/src/ux-sim/mock-seat-pty-frame-child.ts +29 -0
  528. package/src/ux-sim/mock-seat-pty-frame.ts +297 -0
  529. package/src/ux-sim/mock-seat-pty-sequence-child.ts +21 -0
  530. package/src/ux-sim/mock-seat-pty-sequence.ts +206 -0
  531. package/src/ux-sim/next-tui-seat-scenarios.ts +631 -0
  532. package/src/ux-sim/onboarding-first-run-device.ts +269 -0
  533. package/src/ux-sim/pty-key-modifiers-device.ts +85 -0
  534. package/src/ux-sim/subagent-tools-device.ts +81 -0
  535. package/src/ux-sim/task-orchestrator-surfaces-self-implement-pod-device.ts +250 -0
  536. package/src/ux-sim/tty-foreground-steal-child.ts +21 -0
  537. package/src/version/packed-revision.json +1 -1
  538. package/apps/pwa/out/_next/static/chunks/1165.bad13fc9151a7288.js +0 -1
  539. package/apps/pwa/out/_next/static/chunks/13.02a07436dec15f89.js +0 -32
  540. package/apps/pwa/out/_next/static/chunks/1540.af38731fa7c4a3e0.js +0 -41
  541. package/apps/pwa/out/_next/static/chunks/1850.f2b470ba5a471da0.js +0 -24
  542. package/apps/pwa/out/_next/static/chunks/193.62da94cd0b88323b.js +0 -74
  543. package/apps/pwa/out/_next/static/chunks/1975.260f6d249fac5f7c.js +0 -1
  544. package/apps/pwa/out/_next/static/chunks/1982-67cd8dfe8682325f.js +0 -1
  545. package/apps/pwa/out/_next/static/chunks/1993-c0007b7ad0c0ef41.js +0 -1
  546. package/apps/pwa/out/_next/static/chunks/1999-bb415351c8a2846c.js +0 -1
  547. package/apps/pwa/out/_next/static/chunks/1c735de4.f993a30bbd3c57c4.js +0 -1
  548. package/apps/pwa/out/_next/static/chunks/1dc5a567.9b092bfae3d7099b.js +0 -1
  549. package/apps/pwa/out/_next/static/chunks/2085.8f950b4bda1cbbc7.js +0 -1
  550. package/apps/pwa/out/_next/static/chunks/2159-6179a961376956b9.js +0 -1
  551. package/apps/pwa/out/_next/static/chunks/216.b5c2a421aa1d28a5.js +0 -1
  552. package/apps/pwa/out/_next/static/chunks/2251.49fbe369f74c372c.js +0 -148
  553. package/apps/pwa/out/_next/static/chunks/2286-2939577360d3dcc0.js +0 -1
  554. package/apps/pwa/out/_next/static/chunks/2625.5d870aa999f33db1.js +0 -34
  555. package/apps/pwa/out/_next/static/chunks/2668.34d21539e62bf111.js +0 -77
  556. package/apps/pwa/out/_next/static/chunks/2833-0fe47af99ed13c29.js +0 -1
  557. package/apps/pwa/out/_next/static/chunks/2930.d0edb4f108787ef7.js +0 -1
  558. package/apps/pwa/out/_next/static/chunks/2973.77318e7d85f59477.js +0 -62
  559. package/apps/pwa/out/_next/static/chunks/3123.2bee9ba15d320bd2.js +0 -90
  560. package/apps/pwa/out/_next/static/chunks/3162-6cf652bf7124d8cf.js +0 -1
  561. package/apps/pwa/out/_next/static/chunks/3577-7340009f7007a795.js +0 -1
  562. package/apps/pwa/out/_next/static/chunks/3618.f514792d6c59d7fd.js +0 -36
  563. package/apps/pwa/out/_next/static/chunks/3669.343f9119aa276001.js +0 -1
  564. package/apps/pwa/out/_next/static/chunks/3736.c2efad489604cc3b.js +0 -79
  565. package/apps/pwa/out/_next/static/chunks/3817.f9aeab2bb6675d73.js +0 -1
  566. package/apps/pwa/out/_next/static/chunks/3f7c4ee5.3dde54f01a816613.js +0 -122
  567. package/apps/pwa/out/_next/static/chunks/4182-0b9b1251c29cc0cf.js +0 -1
  568. package/apps/pwa/out/_next/static/chunks/4441-875b97f5821ed58a.js +0 -1
  569. package/apps/pwa/out/_next/static/chunks/4582.bd68683c076649e3.js +0 -286
  570. package/apps/pwa/out/_next/static/chunks/4605-6e623f55aec77422.js +0 -1
  571. package/apps/pwa/out/_next/static/chunks/4607.5cc27468628eb4bf.js +0 -1
  572. package/apps/pwa/out/_next/static/chunks/4627.f07508b3936fd6c0.js +0 -24
  573. package/apps/pwa/out/_next/static/chunks/4747-c22262c093e46807.js +0 -1
  574. package/apps/pwa/out/_next/static/chunks/4775-178215aed0daba61.js +0 -1
  575. package/apps/pwa/out/_next/static/chunks/4815-6ffb8c3c991cc45d.js +0 -1
  576. package/apps/pwa/out/_next/static/chunks/4834-beca8b47ad29a898.js +0 -1
  577. package/apps/pwa/out/_next/static/chunks/5023-fb4802b6d2492b02.js +0 -1
  578. package/apps/pwa/out/_next/static/chunks/5154.a118f3957ed14e36.js +0 -1
  579. package/apps/pwa/out/_next/static/chunks/5210-a2e0015e3d4d5d9c.js +0 -1
  580. package/apps/pwa/out/_next/static/chunks/5356.84ac712cc15928bf.js +0 -1
  581. package/apps/pwa/out/_next/static/chunks/5401.22352141b6fd336e.js +0 -23
  582. package/apps/pwa/out/_next/static/chunks/5483-dd5d7697f6bdb106.js +0 -1
  583. package/apps/pwa/out/_next/static/chunks/5662.02eaa029af05ad46.js +0 -1
  584. package/apps/pwa/out/_next/static/chunks/5760.22456f433c6f42c0.js +0 -1
  585. package/apps/pwa/out/_next/static/chunks/5852.db1f7449f4f74612.js +0 -287
  586. package/apps/pwa/out/_next/static/chunks/5891-0f11a7d7362a9072.js +0 -1
  587. package/apps/pwa/out/_next/static/chunks/5943.264d14a44ca32513.js +0 -1
  588. package/apps/pwa/out/_next/static/chunks/604-1b3c39df083c02aa.js +0 -1
  589. package/apps/pwa/out/_next/static/chunks/6121-5f97072330738caf.js +0 -1
  590. package/apps/pwa/out/_next/static/chunks/6193.07d8ca5af4b09086.js +0 -1
  591. package/apps/pwa/out/_next/static/chunks/6203-b3fea3a60d69ff14.js +0 -1
  592. package/apps/pwa/out/_next/static/chunks/6286.d638fb5953e2e757.js +0 -8
  593. package/apps/pwa/out/_next/static/chunks/6301.ae0245f507e4395e.js +0 -1
  594. package/apps/pwa/out/_next/static/chunks/6673.fbbe5699f6ea5991.js +0 -27
  595. package/apps/pwa/out/_next/static/chunks/6777-f6d301efb5de828d.js +0 -1
  596. package/apps/pwa/out/_next/static/chunks/6786.423f24c88de034c7.js +0 -93
  597. package/apps/pwa/out/_next/static/chunks/6854-cef64a948c5e122d.js +0 -1
  598. package/apps/pwa/out/_next/static/chunks/6866-79aa5b4e47cac6aa.js +0 -1
  599. package/apps/pwa/out/_next/static/chunks/6b89c8bd.24df687a820500b3.js +0 -1
  600. package/apps/pwa/out/_next/static/chunks/7037-fe27f001db30d323.js +0 -1
  601. package/apps/pwa/out/_next/static/chunks/7080.2d826860534fd88e.js +0 -1
  602. package/apps/pwa/out/_next/static/chunks/7118.8731be28c3ebb14b.js +0 -1
  603. package/apps/pwa/out/_next/static/chunks/7196.44241bc1df3e26fe.js +0 -149
  604. package/apps/pwa/out/_next/static/chunks/7235.c7c4e6cb5c693c2d.js +0 -104
  605. package/apps/pwa/out/_next/static/chunks/7706-58bfb1546637f217.js +0 -1
  606. package/apps/pwa/out/_next/static/chunks/7839.5b91455338c9fd3f.js +0 -1
  607. package/apps/pwa/out/_next/static/chunks/7857-31f13ee96f4011aa.js +0 -1
  608. package/apps/pwa/out/_next/static/chunks/81.f74cb60312fe4001.js +0 -1
  609. package/apps/pwa/out/_next/static/chunks/8219.baa715d1deee80e2.js +0 -1
  610. package/apps/pwa/out/_next/static/chunks/8256.e3ac8421a71f82f4.js +0 -1
  611. package/apps/pwa/out/_next/static/chunks/8310-444a6a895c402f12.js +0 -1
  612. package/apps/pwa/out/_next/static/chunks/8423.f7dda8aca4fcaae5.js +0 -1
  613. package/apps/pwa/out/_next/static/chunks/8437-8d5492094b166cfa.js +0 -1
  614. package/apps/pwa/out/_next/static/chunks/8496.75649af8652c74f4.js +0 -226
  615. package/apps/pwa/out/_next/static/chunks/8576.65bb654b524f3614.js +0 -1
  616. package/apps/pwa/out/_next/static/chunks/8609.558ce2f8f9aea2be.js +0 -64
  617. package/apps/pwa/out/_next/static/chunks/8614.caec96cfb91f4ae7.js +0 -1
  618. package/apps/pwa/out/_next/static/chunks/8732.367f44a916202353.js +0 -1
  619. package/apps/pwa/out/_next/static/chunks/9141-7fd1f73a5400bdc2.js +0 -1
  620. package/apps/pwa/out/_next/static/chunks/921.c4267d0d69a65404.js +0 -1
  621. package/apps/pwa/out/_next/static/chunks/9261.3962c3321b047691.js +0 -115
  622. package/apps/pwa/out/_next/static/chunks/936-26efd1a0efdd44d3.js +0 -1
  623. package/apps/pwa/out/_next/static/chunks/9627-4b9efe616b27e2e2.js +0 -1
  624. package/apps/pwa/out/_next/static/chunks/967-2aa39b2d04b4c690.js +0 -1
  625. package/apps/pwa/out/_next/static/chunks/9734.40eb1e558c29ccea.js +0 -1
  626. package/apps/pwa/out/_next/static/chunks/9908.3aa54f7a586a73dc.js +0 -1
  627. package/apps/pwa/out/_next/static/chunks/9954-ca8d29752bdbb45a.js +0 -1
  628. package/apps/pwa/out/_next/static/chunks/9974-507fa67cb94066dd.js +0 -1
  629. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-ce9e00f7fb45506a.js +0 -1
  630. package/apps/pwa/out/_next/static/chunks/app/approvals/page-fa59d6d17daa3389.js +0 -1
  631. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-53f68bbe7bd4b774.js +0 -1
  632. package/apps/pwa/out/_next/static/chunks/app/board/page-1c500fbb9f4a7a04.js +0 -1
  633. package/apps/pwa/out/_next/static/chunks/app/botlab/page-99d72673aa5c307c.js +0 -1
  634. package/apps/pwa/out/_next/static/chunks/app/bots/page-d2d5adc1e9484c88.js +0 -1
  635. package/apps/pwa/out/_next/static/chunks/app/ceo/page-c607c5aae637f1cc.js +0 -1
  636. package/apps/pwa/out/_next/static/chunks/app/chat/page-fa8c23596d555c0f.js +0 -1
  637. package/apps/pwa/out/_next/static/chunks/app/consult/page-bdab8c5e855c8416.js +0 -1
  638. package/apps/pwa/out/_next/static/chunks/app/control/page-406b47745b53f269.js +0 -1
  639. package/apps/pwa/out/_next/static/chunks/app/decisions/page-a4276386b7051f50.js +0 -1
  640. package/apps/pwa/out/_next/static/chunks/app/design-check/page-8a26bab9ad375f25.js +0 -1
  641. package/apps/pwa/out/_next/static/chunks/app/editor/page-775f48aa1dc7a2d5.js +0 -1
  642. package/apps/pwa/out/_next/static/chunks/app/exec/page-1c4dd5f959d0c558.js +0 -1
  643. package/apps/pwa/out/_next/static/chunks/app/field/page-c48938a8377c9f70.js +0 -1
  644. package/apps/pwa/out/_next/static/chunks/app/healthz/route-ce9e00f7fb45506a.js +0 -1
  645. package/apps/pwa/out/_next/static/chunks/app/inside/page-c6e7905859425aa9.js +0 -1
  646. package/apps/pwa/out/_next/static/chunks/app/intake/page-c280f4252452e830.js +0 -1
  647. package/apps/pwa/out/_next/static/chunks/app/layout-cf8c57498f20970a.js +0 -1
  648. package/apps/pwa/out/_next/static/chunks/app/live/page-ca3a17caf409fc83.js +0 -1
  649. package/apps/pwa/out/_next/static/chunks/app/live-run/page-26e6c18a1a750cf6.js +0 -1
  650. package/apps/pwa/out/_next/static/chunks/app/loops/page-3abc872cc6aeee39.js +0 -1
  651. package/apps/pwa/out/_next/static/chunks/app/market/page-f7481b7b2174fe3d.js +0 -1
  652. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1ae7ef8077680337.js +0 -1
  653. package/apps/pwa/out/_next/static/chunks/app/morning/page-47c5d4db587266bd.js +0 -1
  654. package/apps/pwa/out/_next/static/chunks/app/not-found-415f1e243c7ccb13.js +0 -1
  655. package/apps/pwa/out/_next/static/chunks/app/observatory/page-73f380f17b76f95c.js +0 -1
  656. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-2991073ad66ee2d9.js +0 -1
  657. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-1f5a60f70cb4dd0c.js +0 -1
  658. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fbad60d1e358fb2c.js +0 -1
  659. package/apps/pwa/out/_next/static/chunks/app/outputs/page-bac7dee94e5eae4a.js +0 -1
  660. package/apps/pwa/out/_next/static/chunks/app/page-398047d6949e8ca5.js +0 -1
  661. package/apps/pwa/out/_next/static/chunks/app/reflection/page-2d33a2231540ca89.js +0 -1
  662. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-5bf1e6bf70bcb96c.js +0 -1
  663. package/apps/pwa/out/_next/static/chunks/app/sessions/page-ba59c19376a0917d.js +0 -1
  664. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-111f187139add30f.js +0 -1
  665. package/apps/pwa/out/_next/static/chunks/app/settings/page-acaae197d5be5779.js +0 -1
  666. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-0df20b0f546a0d7d.js +0 -1
  667. package/apps/pwa/out/_next/static/chunks/app/setup/layout-ce9e00f7fb45506a.js +0 -1
  668. package/apps/pwa/out/_next/static/chunks/app/setup/page-27e1202d24b8fe35.js +0 -1
  669. package/apps/pwa/out/_next/static/chunks/app/share/page-bda78c50a78363e5.js +0 -1
  670. package/apps/pwa/out/_next/static/chunks/app/showroom/page-da592691293d9c9f.js +0 -1
  671. package/apps/pwa/out/_next/static/chunks/app/tasks/page-f832994317a2e9f0.js +0 -1
  672. package/apps/pwa/out/_next/static/chunks/app/term/page-dc0a532f5b9196ec.js +0 -1
  673. package/apps/pwa/out/_next/static/chunks/app/today/page-cfccbbeae0473404.js +0 -1
  674. package/apps/pwa/out/_next/static/chunks/app/trace/page-527e2073af84c81b.js +0 -1
  675. package/apps/pwa/out/_next/static/chunks/app/vault/page-bac86e9112671357.js +0 -1
  676. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6d59dff410e9d76a.js +0 -1
  677. package/apps/pwa/out/_next/static/chunks/app/workflows/page-53a61f8be09f02d2.js +0 -1
  678. package/apps/pwa/out/_next/static/chunks/app/workspace/page-db8f559e763c17f9.js +0 -1
  679. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c02aa4cc66455093.js +0 -1
  680. package/apps/pwa/out/_next/static/chunks/framework-50e6acebc630849b.js +0 -1
  681. package/apps/pwa/out/_next/static/chunks/main-7a1ae342d5ed75e6.js +0 -1
  682. package/apps/pwa/out/_next/static/chunks/main-app-7ad7d505ba1f92b4.js +0 -1
  683. package/apps/pwa/out/_next/static/chunks/pages/_app-02edba75232cb235.js +0 -1
  684. package/apps/pwa/out/_next/static/chunks/pages/_error-97d6ebae3e8eee4a.js +0 -1
  685. package/apps/pwa/out/_next/static/chunks/webpack-e986adaa429163e8.js +0 -1
  686. /package/apps/pwa/out/_next/static/{T1mBQqmkwKDLGtyisDCOd → uC-N0emedeNOFuaBQFv-I}/_ssgManifest.js +0 -0
@@ -12,6 +12,7 @@ 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
17
  import { mergePartialFailures, readPartialPlanResult, testFileOfId, verifyDocsOnlyCarry } from './gate-partial';
17
18
  import { planGateRadiusShadow, shadowActual, writeGateRadiusShadow, type GateRadiusShadowPlan } from './gate-radius-shadow';
@@ -80,7 +81,9 @@ interface CommandResult { rc: number; output: string; passedIds?: string[]; stal
80
81
  /** GATE-POD-STALL-FAILFAST ① — shard Pods whose tests the mid-run watch aborted in this sweep. */
81
82
  abortedShards?: number;
82
83
  /** Each contributing Pod leaf already passed failuresOf; repeated printed ids cannot add new failures. */
83
- 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 }
84
87
  function redactedGateLogTail(input: string): string {
85
88
  // Redact before trimming: a truncated token might no longer match the redaction rule.
86
89
  const bytes = Buffer.from(redactSecretText(input), 'utf8');
@@ -88,6 +91,25 @@ function redactedGateLogTail(input: string): string {
88
91
  while (start < bytes.length && (bytes[start]! & 0xc0) === 0x80) start++;
89
92
  return bytes.subarray(start).toString('utf8');
90
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
+ }
91
113
  export interface GateRunner {
92
114
  command(cmd: string, args: string[], cwd: string, limitMs?: number): Promise<CommandResult>;
93
115
  localCommand(cmd: string, args: string[], cwd: string, limitMs?: number): Promise<CommandResult>;
@@ -103,6 +125,8 @@ export interface GateRunner {
103
125
  removeSnapshot(tree: string): Promise<void>;
104
126
  /** Absolute remote bare mirror path, set before preparing the remote trees. */
105
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;
106
130
  }
107
131
  export interface GateOptions {
108
132
  commit: string;
@@ -130,6 +154,8 @@ export interface GateOptions {
130
154
  /** Opt-in gate shadow: provider fills the fenced lease identity. The Job remains authoritative. */
131
155
  warmReplay?: (input: Omit<WarmWorkerCommand, 'leaseId' | 'generation' | 'workerId' | 'key'>) => Promise<{ command: WarmWorkerCommand | null; transport: WarmPodTransportResult }>;
132
156
  warmQuarantine?: (command: WarmWorkerCommand, reason: string) => Promise<void>;
157
+ /** Explicit gate-profile producer handoff; shadow-only callers keep their Job path. */
158
+ warmProducerEnabled?: boolean;
133
159
  warmFinalize?: (command: WarmWorkerCommand) => Promise<void>;
134
160
  /** Set only on the cut recheck; baseline Jobs never consume the cut worker. */
135
161
  warmCutSha?: string;
@@ -185,6 +211,8 @@ export interface GateOptions {
185
211
  abortTests?: (job: { name: string; context?: string }, reason: string) => boolean | void;
186
212
  /** GATE-LIVE-OBS — the shard state file (`gateShardsPath(version)`) this sweep keeps current; absent = not written. */
187
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[] };
188
216
  /** GATE-MULTI-HOST — is this pool member reachable (kubectl ns ⊕ ssh)? Injected by tests; absent = the real probe on
189
217
  * the gate's own pool · an injected pool without it never marks a member down (unchanged). */
190
218
  probeMember?: (member: PodPoolMember) => boolean;
@@ -202,7 +230,7 @@ export function withWarmGateHooks(opts: GateOptions, config: unknown, injectedRu
202
230
  && config.pod && typeof config.pod === 'object' && 'warmReuse' in config.pod
203
231
  ? config.pod.warmReuse as WarmReuseSwitchConfig : undefined;
204
232
  return { ...opts, pod: { ...opts.pod, warmReplay: requestWarmPodReplay,
205
- warmFinalize: finalizeWarmPodReplay, warmQuarantine: quarantineWarmPodReplay, warmReuseConfig } };
233
+ warmFinalize: finalizeWarmPodReplay, warmQuarantine: quarantineWarmPodReplay, warmProducerEnabled: true, warmReuseConfig } };
206
234
  }
207
235
  export interface GateResult {
208
236
  outcome: 'ok' | 'regression' | 'error';
@@ -531,6 +559,71 @@ export function shardParts(paths: readonly string[], seconds: (file: string) =>
531
559
  if (current.length) parts.push(current);
532
560
  return parts;
533
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
+
534
627
  /**
535
628
  * GATE-ENV-BASELINE (W5 · 10-15) — the gate shard Pod's deterministic test env, exported before anything runs.
536
629
  * 0.2.29 gate failures were the Pod's «environment», not the code: no LLM provider · `man` stubbed · CLI spawn timeouts.
@@ -554,7 +647,8 @@ export function gateEnvBaselineLine(env: Readonly<Record<string, string>> = gate
554
647
  * `shard.timeout` so the artifact reader does not mistake an unfinished child for a test verdict.
555
648
  * A part without `.rc` did not finish. The shell itself always exits 0 (as before).
556
649
  */
557
- 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');
558
652
  // The planner keeps spawn files in their own shards; reject a mixed part rather than silently lose its limit.
559
653
  for (const part of o.parts) {
560
654
  if (part.some(isGateSpawnLaneFile) && !part.every(isGateSpawnLaneFile)) throw new Error('gate spawn lane part mixed with regular tests');
@@ -597,6 +691,7 @@ export function gateShardShell(o: { parts: readonly (readonly string[])[]; cdpPa
597
691
  ...(o.beatSeconds ? gateBeatScript(o.beatSeconds, o.installSlots > 0) : []),
598
692
  ...(o.cachePrefix ? [o.cachePrefix] : []),
599
693
  gateEnvBaselineLine(),
694
+ ...(o.rulesBaseSha ? [`export ELANOUS_GATE_BASE_SHA=${quote(o.rulesBaseSha)}`] : []),
600
695
  // Seconds since this container's PID 1 started (the clone ran before this shell) — 0 when /proc cannot tell.
601
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)`,
602
697
  'case "$pod_age" in ""|*[!0-9]*) pod_age=0;; esac',
@@ -1267,9 +1362,9 @@ export const gateAdmissionWaitText = (reading: string | null) => `메모리 여
1267
1362
  * the shard runs again on the others — one dead host no longer stops the gate. One-member pools keep the old behaviour.
1268
1363
  */
1269
1364
  export const GATE_MEMBER_UNREADABLE_SECONDS_DEFAULT = 600;
1270
- export function defaultGateMemberProbe(member: PodPoolMember): boolean {
1271
- if (!checkPodPool([member], defaultKubectl).ok) return false;
1272
- 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;
1273
1368
  }
1274
1369
 
1275
1370
  export function poolGateAdmission(pool: PodPoolScheduler): GateAdmission {
@@ -1303,8 +1398,27 @@ export async function admitGateShard(admission: GateAdmission | undefined, o: {
1303
1398
  }
1304
1399
  }
1305
1400
 
1306
- 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 {
1307
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;
1308
1422
  // GATE-HOSTMIRROR-EXPLICIT: resolved once per runner and handed to every podCommand (shard · retry · recheck).
1309
1423
  let gateMirror: GateHostMirror | undefined;
1310
1424
  const gateHostMirror = (pod: NonNullable<GateOptions['pod']>): { hostMirror?: string } => {
@@ -1325,14 +1439,19 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1325
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).
1326
1440
  // No coreutils timeout on the remote → run unlimited there and rely on the local ssh deadline.
1327
1441
  const limitPrefix = limitMs ? `$(command -v timeout >/dev/null 2>&1 && echo 'timeout -k 10 ${Math.ceil(limitMs / 1000)}') ` : '';
1328
- // Start in /tmp, not the caller checkout. macOS resolves that symlink to /private/tmp before the child sees it;
1329
- // the remote command still `cd`s to the quoted path, so either spelling is the same directory.
1330
- const run = spawnSync('ssh', [remote, `PATH=$HOME/.bun/bin:/opt/homebrew/bin:$PATH; export PATH; cd ${quote(cwd)} && ${limitPrefix}${[cmd, ...args].map(quote).join(' ')}`],
1331
- { cwd: '/tmp', encoding: 'utf8', maxBuffer: 128 * 1024 * 1024, ...(limitMs ? { timeout: limitMs + 60_000, killSignal: 'SIGKILL' as const } : {}) });
1332
- const timedOut = !!limitMs && (run.status === 124 || run.status === 137 || (run.error as NodeJS.ErrnoException | undefined)?.code === 'ETIMEDOUT');
1333
- 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);
1334
1443
  }
1335
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
+ };
1336
1455
  const podSweep = async (tree: string, logDir: string | undefined, pod: NonNullable<GateOptions['pod']>, only?: readonly string[]): Promise<CommandResult> => {
1337
1456
  const sweepId = randomUUID();
1338
1457
  const shardCount = pod.shards ?? 24;
@@ -1346,7 +1465,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1346
1465
  check(head, 'cut tree HEAD');
1347
1466
  const commit = head.output.trim();
1348
1467
  if (!/^[0-9a-f]{40}$/.test(commit)) throw new Error('invalid cut tree HEAD');
1349
- const selectionStart = Date.now();
1468
+ const selectionStart = clock();
1350
1469
  const listed = await command('git', ['ls-files', '*.test.*'], tree);
1351
1470
  check(listed, 'git ls-files tests');
1352
1471
  const allFiles = listed.output.split(/\r?\n/).filter((file) => /\.test\.(?:tsx?|jsx?|mts|cts)$/.test(file)).sort();
@@ -1356,6 +1475,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1356
1475
  const missing = only?.filter((file) => !allFiles.includes(file)) ?? [];
1357
1476
  if (missing.length) throw new Error(`partial plan file missing at this commit: ${missing.slice(0, 5).join(', ')}`);
1358
1477
  const files = onlySet ? allFiles.filter((file) => onlySet.has(file)) : allFiles;
1478
+ const rulesBaseSha = await rulesGateBaseFor(commit, files);
1359
1479
  if (files.some((file) => file.startsWith('-') || file.startsWith('/') || file.startsWith('./')
1360
1480
  || file.split('/').some((part) => !part || part === '.' || part === '..') || /[\r\n]/.test(file))) throw new Error('unsafe pod test path');
1361
1481
  if (new Set(files).size !== files.length) throw new Error('duplicate pod test path');
@@ -1375,12 +1495,14 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1375
1495
  // The TD1 measurements predate the fixture-only gate; do not isolate those five files at their old 2–13 GB peaks.
1376
1496
  const fixtureOnly = new Set([...GATE_NIGHTLY_AUDITS, 'test/guardian/dispatch-surface-contract.test.ts', 'test/user-config-mcp.test.ts']);
1377
1497
  const heavy = new Set(assignable.filter((file) => !fixtureOnly.has(file) && (memory.get(file) ?? 0) >= POD_HEAVY_FILE_MB));
1378
- const { shards: lightShards, spawnFiles: spawnLane, spawnShardCount } = planGateSpawnSeparatedShards(assignable.filter((file) => !heavy.has(file)), durations, shardCount);
1379
- const shards = [
1380
- ...lightShards,
1381
- ...[...heavy].map((file) => ({ files: [file], plannedSeconds: durations.get(file) ?? 0 })),
1382
- ];
1383
- 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 });
1384
1506
  logPlanWalltime(planWall, shards);
1385
1507
  if (heavy.size) debug.log('release-loop.gate', 'pod-heavy-alone', { files: [...heavy], thresholdMb: POD_HEAVY_FILE_MB, memoryLimit: POD_ISOLATION_MEMORY_LIMIT });
1386
1508
  const known = assignable.filter((file) => durations.has(file)).length;
@@ -1389,14 +1511,14 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1389
1511
  maxPlannedSeconds: Math.max(...shards.map((shard) => shard.plannedSeconds)),
1390
1512
  source: durations.size ? pod.durationSource : 'none',
1391
1513
  });
1392
- reportSelectionTimed(Date.now() - selectionStart, assignable.length, pod.onSelectionTimed);
1514
+ reportSelectionTimed(clock() - selectionStart, assignable.length, pod.onSelectionTimed);
1393
1515
  // GATE-SPEED A3①: shard Pod CPU (`release.loop.gatePodCpu` · default request 1 / limit 4) — shards × request must fit the pool.
1394
1516
  const cpu = resolvePodCpu(pod.cpu);
1395
1517
  // Only evidence-backed opt-in files reach this batch; every other file retains its own Pod below.
1396
1518
  const batchWidth = Math.max(1, Math.min(4, Math.floor(podCpuMillis(cpu.limit)! / 1000)));
1397
1519
  debug.log('release-loop.gate', 'shard-resources', { shards: shards.length, cpuRequest: cpu.request, cpuLimit: cpu.limit, cpuSource: pod.cpu ? 'config' : 'default' });
1398
1520
  debug.log('release-loop.gate', 'isolation-batch-width', { batchWidth, cpuLimit: cpu.limit });
1399
- const ready = poolOverride ? undefined : checkPodPool(parsePodPool(pod.pool), defaultKubectl);
1521
+ const ready = poolOverride ? undefined : checkPodPool(parsePodPool(pod.pool), kubectl);
1400
1522
  if (ready && !ready.ok) throw new Error('pod command: 풀의 노드가 하나도 준비되지 않았다');
1401
1523
  const poolScheduler = poolOverride ?? new PodPoolScheduler(ready!.ready);
1402
1524
  const configCache = typeof pod.bunCache === 'string' ? pod.bunCache.trim() : '';
@@ -1412,7 +1534,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1412
1534
  const admissionWaitSeconds = pod.admissionWaitSeconds ?? GATE_ADMISSION_WAIT_SECONDS_DEFAULT;
1413
1535
  if (!Number.isSafeInteger(admissionWaitSeconds) || admissionWaitSeconds < 0) throw new Error('invalid pod sweep options');
1414
1536
  // An injected pool (tests) runs without admission unless `pod.admission` is passed; the gate's own pool asks it.
1415
- const gateAdmission = pod.admission ?? (poolOverride ? undefined : poolGateAdmission(poolScheduler));
1537
+ const gateAdmission = pod.admission ?? tools.admission ?? (poolOverride ? undefined : poolGateAdmission(poolScheduler));
1416
1538
  debug.log('release-loop.gate', 'pod-admission', { waitSeconds: admissionWaitSeconds, source: pod.admissionWaitSeconds !== undefined ? 'config' : 'default', enabled: !!gateAdmission && admissionWaitSeconds > 0 });
1417
1539
  // GATE-POD-STALL-FAILFAST: a shard with no test output within the stall window is stopped and recorded unmeasured.
1418
1540
  const stallSeconds = pod.stallSeconds ?? GATE_POD_STALL_SECONDS_DEFAULT;
@@ -1421,14 +1543,14 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1421
1543
  // An injected pool (tests) runs without the watch unless `pod.liveLog` is passed — never a real kubectl from a test.
1422
1544
  const liveLog = pod.liveLog ?? (poolOverride ? undefined : (job: { name: string; context?: string }) => {
1423
1545
  if (!job.context) return null;
1424
- 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']);
1425
1547
  return logs.status === 0 ? logs.stdout : null;
1426
1548
  });
1427
1549
  const stopJob = pod.stopJob ?? ((job: { name: string; context?: string }) => {
1428
1550
  // Not `delete job`: runPodCommand would poll a missing Job forever. A 1 s deadline makes the Job fail now and
1429
1551
  // runPodCommand returns through its normal path (logs · exit code · Secret cleanup).
1430
1552
  if (!job.context) return false;
1431
- 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 } })]);
1432
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' });
1433
1555
  return patched.status === 0;
1434
1556
  });
@@ -1439,7 +1561,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1439
1561
  if (!Number.isFinite(midRunStallSeconds) || midRunStallSeconds < 0) throw new Error('invalid pod sweep options');
1440
1562
  const abortTests = pod.abortTests ?? (poolOverride ? undefined : (job: { name: string; context?: string }, reason: string) => {
1441
1563
  if (!job.context) return false;
1442
- 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)]);
1443
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' });
1444
1566
  return ran.status === 0;
1445
1567
  });
@@ -1451,7 +1573,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1451
1573
  const downMembers = new Set<string>();
1452
1574
  let tailPlacedHost: string | undefined;
1453
1575
  const multiHost = poolScheduler.members.length > 1;
1454
- const probeMember = pod.probeMember ?? (poolOverride ? undefined : defaultGateMemberProbe);
1576
+ const probeMember = pod.probeMember ?? (poolOverride ? undefined : (member: PodPoolMember) => defaultGateMemberProbe(member, { kubectl, remoteRun }));
1455
1577
  const memberUnreadableSeconds = pod.memberUnreadableSeconds ?? GATE_MEMBER_UNREADABLE_SECONDS_DEFAULT;
1456
1578
  if (!Number.isFinite(memberUnreadableSeconds) || memberUnreadableSeconds < 0) throw new Error('invalid pod sweep options');
1457
1579
  debug.log('release-loop.gate', 'pod-roster', { members: poolScheduler.members.map((m) => ({ context: m.context, host: m.sshHost ?? null, capacity: m.capacity })),
@@ -1472,7 +1594,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1472
1594
  const jobLogTail = (job: PodCommandResult): string => {
1473
1595
  if (podLogTail) return podLogTail(job);
1474
1596
  for (const member of poolScheduler.members.filter((m) => !downMembers.has(m.context))) {
1475
- 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']);
1476
1598
  if (logs.status === 0 && logs.stdout) return logs.stdout;
1477
1599
  }
1478
1600
  return '';
@@ -1484,7 +1606,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1484
1606
  let termination: { reason?: string; message?: string; exitCode?: number; finishedAt?: string } | undefined;
1485
1607
  let podName: string | undefined;
1486
1608
  let latestEndedAt = -Infinity;
1487
- const status = (pod.failureKubectl ?? defaultKubectl)([...prefix, 'get', 'pods', '-l', `job-name=${job.name}`, '-o', 'json']);
1609
+ const status = (pod.failureKubectl ?? kubectl)([...prefix, 'get', 'pods', '-l', `job-name=${job.name}`, '-o', 'json']);
1488
1610
  if (status.status !== 0) errors.push(`Pod 상태: ${status.stderr.trim() || `kubectl rc=${status.status}`}`);
1489
1611
  else try {
1490
1612
  const pods = JSON.parse(status.stdout) as { items?: Array<{ metadata?: { name?: string; creationTimestamp?: string }; status?: { containerStatuses?: Array<{ name?: string; state?: { terminated?: typeof termination } }> } }> };
@@ -1498,7 +1620,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1498
1620
  }
1499
1621
  if (!termination) errors.push('Pod 상태: child 종료 사유 없음');
1500
1622
  } catch (error) { errors.push(`Pod 상태 JSON: ${String(error)}`); }
1501
- const logs = podName ? (pod.failureKubectl ?? defaultKubectl)([...prefix, 'logs', `pod/${podName}`, '-c', 'child', '--tail=100']) : undefined;
1623
+ const logs = podName ? (pod.failureKubectl ?? kubectl)([...prefix, 'logs', `pod/${podName}`, '-c', 'child', '--tail=100']) : undefined;
1502
1624
  if (!logs) errors.push('로그: 종료된 Pod 이름 없음');
1503
1625
  else if (logs.status !== 0) errors.push(`로그: ${logs.stderr.trim() || `kubectl rc=${logs.status}`}`);
1504
1626
  return { ...(podName ? { podName } : {}), ...(termination ? { termination } : {}), ...(logs?.status === 0 ? { logTail: logs.stdout } : {}),
@@ -1572,11 +1694,11 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1572
1694
  const shardDeadline = shardDeadlineSeconds(batchIsolation ? Math.max(...paths.map((file) => estimated([file]))) * Math.ceil(paths.length / batchWidth) : estimated(paths), deadlineSeconds);
1573
1695
  const parts = batchIsolation ? paths.map((file) => [file]) : shardParts(paths, (file) => estimated([file]));
1574
1696
  const memoryLimit = batchIsolation || paths.length === 1 && (depth >= 1 || heavy.has(paths[0]!)) ? POD_ISOLATION_MEMORY_LIMIT : undefined;
1575
- 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 } : {}) };
1576
1698
  const keyShell = gateShardShell(shellOptions);
1577
1699
  // The stall beat only reports; runKey keeps hashing the shell without it, so earlier results stay reusable.
1578
1700
  const runKey = createHash('sha256').update(JSON.stringify({ pool: pod.pool, podShell: keyShell, memoryLimit: memoryLimit ?? null, deadlineSeconds: shardDeadline, bunCache: bunCache ?? null })).digest('hex');
1579
- let start = Date.now();
1701
+ let start = clock();
1580
1702
  let admitted: GateAdmissionResult | undefined;
1581
1703
  let output: string | undefined;
1582
1704
  const phaseTimes: Record<string, string> = {};
@@ -1648,7 +1770,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1648
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 } : {}) },
1649
1771
  admitted.outcome === 'granted' ? undefined : { level: 'warn' });
1650
1772
  // The admission wait is not the shard's run time: deadline/timeout judgment and the measured duration start here.
1651
- start = Date.now();
1773
+ start = clock();
1652
1774
  const jobName = `gate-${randomUUID()}`;
1653
1775
  jobNameForCapture = jobName;
1654
1776
  // The member's context is known once the Job is placed; the live log is read (and the Job stopped) there.
@@ -1671,7 +1793,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1671
1793
  },
1672
1794
  } : {}) }) : undefined;
1673
1795
  const placed = admitted.release;
1674
- const job = await podCommand({
1796
+ const job = await runPod({
1675
1797
  provenanceGate: true,
1676
1798
  provenance: { actorKind: process.env.ELANOUS_SEAT ? 'seat' : 'loop', actor: process.env.ELANOUS_SEAT || 'release-loop',
1677
1799
  character: 'gate-shard-runner', launchHost: hostname(), entrance: process.env.ELANOUS_RELEASE_ENTRANCE?.trim() || 'release-run',
@@ -1700,7 +1822,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1700
1822
  // Production pool sweeps always use this Kubernetes lookup; lookup failure is receipt-invalid only.
1701
1823
  if (!poolOverride && placedOn) {
1702
1824
  try {
1703
- const got = defaultKubectl(['--context', placedOn, '-n', 'elanous-test', 'get', 'pods', '-l', `job-name=${job.job}`, '-o', 'json']);
1825
+ const got = kubectl(['--context', placedOn, '-n', 'elanous-test', 'get', 'pods', '-l', `job-name=${job.job}`, '-o', 'json']);
1704
1826
  if (got.status === 0) {
1705
1827
  podLifecycle = readGatePodLifecycle(got.stdout);
1706
1828
  imageDigest = actualGatePodImageDigest(got.stdout);
@@ -1733,7 +1855,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1733
1855
  if (abort?.taken) abortedShards++;
1734
1856
  if (stalledAt && !finished.length && rc === undefined) stall = { seconds: stallSeconds, sinceCheckoutMs: stalledAt.sinceCheckoutMs };
1735
1857
  // A Job the cluster cut at its own deadline leaves no outbox at all: past the deadline with nothing is a timeout too.
1736
- 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;
1737
1859
  lastFile = output === undefined && unfinishedLog === undefined ? undefined : lastStartedTestFile(`${output ?? ''}\n${unfinishedLog ?? ''}`, paths);
1738
1860
  if (!lastFile && (job.exitCode !== 0 || rc === undefined || (rc !== 0 && rc !== 1))) {
1739
1861
  try { lastFile = lastStartedTestFile(jobLogTail(job).split(/\r?\n/).slice(-200).join('\n'), paths); }
@@ -1746,7 +1868,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1746
1868
  }
1747
1869
  // A launch that failed before placement still gives its admission back (release is idempotent).
1748
1870
  admitted?.release();
1749
- const durationMs = Date.now() - start;
1871
+ const durationMs = clock() - start;
1750
1872
  const fileSeconds = junit === undefined ? undefined : Object.fromEntries(readJunitFileSeconds(junit));
1751
1873
  const testSeconds = fileSeconds === undefined ? undefined : Object.values(fileSeconds).reduce((sum, seconds) => sum + seconds, 0);
1752
1874
  const testWallSeconds = batchIsolation
@@ -1794,7 +1916,8 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1794
1916
  ...(fileSeconds !== undefined ? { fileSeconds, testSeconds } : {}), ...(testWallSeconds !== undefined ? { testWallSeconds } : {}),
1795
1917
  ...(podOverheadSeconds !== undefined ? { podOverheadSeconds } : {}),
1796
1918
  ...(unfinishedLogBytes !== undefined ? { unfinishedLogBytes } : {}),
1797
- ...(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 } : {}) } : {}),
1798
1921
  ...(abort ? { abort: { reason: `abort: ${abort.reason}`, at: abort.at, by: abort.by, sinceLastOutputMs: abort.sinceLastOutputMs, taken: abort.taken } } : {}),
1799
1922
  stallWatch: shardStallWatchRecord(stallSeconds, !!liveLog, stallWatchState) }) + '\n', { mode: 0o600 });
1800
1923
  if (paths.length === 1 && (rc === 0 || rc === 1) && !jobFailed && jobExitCode === 0
@@ -2009,9 +2132,13 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2009
2132
  ran: Number(ran![1]), files: Number(ran![2]), passedIds: junit ? junitPassedIds(junit) : [] }], stalled: [] };
2010
2133
  };
2011
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[] = [];
2012
2137
  const settled = await Promise.allSettled(shards.map(async (item, shard) => {
2138
+ const shardStartedAt = clock();
2013
2139
  try {
2014
2140
  const done = await runShard(item.files, shard);
2141
+ shardDurations[shard] = clock() - shardStartedAt;
2015
2142
  const state = !done.stalled.length ? 'done' as const : done.stalled.some((stall) => stall.reason === 'timeout') ? 'timeout' as const : 'failed' as const;
2016
2143
  markShard(shard, { state, endedAt: new Date().toISOString() }, ['waitReason']);
2017
2144
  return done;
@@ -2023,6 +2150,16 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2023
2150
  const rejected = settled.find((result): result is PromiseRejectedResult => result.status === 'rejected');
2024
2151
  if (rejected) throw rejected.reason;
2025
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
+ }
2026
2163
  let stalledShards = results.flatMap((result) => result.stalled);
2027
2164
  const measured = results.flatMap((result) => result.runs);
2028
2165
  // GATE-STALLED-TAIL-REMEASURE: after the sweep, give only a one-file no-output leaf one fresh attempt on a different
@@ -2079,7 +2216,8 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2079
2216
  podSweep: { id: sweepId, shards: shards.length, files: assignable },
2080
2217
  passedIds: [...new Set(measured.flatMap((run) => run.passedIds))].sort(),
2081
2218
  stalledEnv: stalledShards.flatMap((item) => item.files.map((file) => ({ file, reason: 'no-output' as const }))),
2082
- ...(abortedShards ? { abortedShards } : {}) };
2219
+ ...(abortedShards ? { abortedShards } : {}),
2220
+ ...(riskLane ? { riskLane } : {}) };
2083
2221
  };
2084
2222
  const runPodRecheckBatch = async (files: readonly string[], commit: string, pod: NonNullable<GateOptions['pod']>, timeoutMs: number, relaxed = false): Promise<Map<string, CommandResult>> => {
2085
2223
  const warmShadowScope = pod.warmShadowScope ?? { requested: false };
@@ -2102,6 +2240,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2102
2240
  || file.split('/').some((part) => part === '.' || part === '..')))
2103
2241
  throw new Error('unsafe pod recheck files');
2104
2242
  if (!sha.test(commit)) throw new Error('invalid pod recheck commit');
2243
+ const rulesBaseSha = await rulesGateBaseFor(commit, files);
2105
2244
  const installSlots = pod.installSlots ?? POD_INSTALL_SLOTS_DEFAULT;
2106
2245
  if (!Number.isSafeInteger(installSlots) || installSlots < 0) throw new Error('invalid pod recheck install slots');
2107
2246
  const bunCache = pod.bunCache?.trim() || process.env[POD_BUN_CACHE_HOST_PATH]?.trim();
@@ -2110,24 +2249,51 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2110
2249
  const deadlineSeconds = partLimitSeconds * Math.ceil(files.length / 2) + POD_SHARD_EMIT_RESERVE_SECONDS + 60;
2111
2250
  const shell = gateShardShell({ parts: files.map((file) => [file]), cdpPatterns: [], softSeconds: softDeadlineSeconds(deadlineSeconds),
2112
2251
  cachePrefix: bunCache ? podBunCacheVolume(bunCache).shellPrefix : '', installSlots, parallelParts: files.length > 1 ? 2 : 1,
2113
- partLimitSeconds, sourceBinding: { cutSha: commit, attemptId }, ...(relaxed ? { testTimeoutMs: GATE_TIMEOUT_RECHECK_MS } : {}) });
2114
- const job = await podCommand({ pool: pod.pool, ...(poolOverride ? { poolScheduler: poolOverride } : {}), clone: true,
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,
2115
2277
  provenanceGate: true,
2116
2278
  provenance: { actorKind: process.env.ELANOUS_SEAT ? 'seat' : 'loop', actor: process.env.ELANOUS_SEAT || 'release-loop',
2117
2279
  character: 'gate-shard-runner', launchHost: hostname(), entrance: 'release-recheck', kind: 'gate-recheck',
2118
2280
  run: process.env.ELANOUS_RUN_ID || attemptId, phase: 'release-gate', requestedAt: new Date().toISOString(),
2119
2281
  graphNode: 'gate-triage', commit, reason: `release gate recheck ${files.length} files`, pool: pod.pool,
2120
- memReq: POD_ISOLATION_MEMORY_LIMIT, warmReused: false },
2121
- source: { kind: 'commit', sha: commit }, deadlineSeconds, ...(bunCache ? { bunCache } : {}), ...gateHostMirror(pod),
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),
2122
2285
  ...(installSlots > 0 ? { installSlots: POD_INSTALL_SLOTS_HOST_PATH } : {}), ...(pod.cpu ? { cpu: pod.cpu } : {}),
2123
2286
  memoryLimit: POD_ISOLATION_MEMORY_LIMIT, name: `gate-recheck-${randomUUID()}`, command: ['bash', '-lc', shell] });
2124
- const artifact = (name: string) => {
2125
- const path = join(job.artifactsDir, name);
2126
- return existsSync(path) ? readFileSync(path, 'utf8') : undefined;
2127
- };
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
+ };
2128
2296
  const installed = artifact('install.rc')?.trim() === '0';
2129
- const warmEligible = files.length === 1 && pod.warmEligible === true && !!pod.warmReplay && !!pod.warmQuarantine && !!pod.warmFinalize
2130
- && pod.warmCutSha === commit;
2131
2297
  const incomplete = (message: string, file: string): Error => warmEligible
2132
2298
  ? new WarmRecheckIncomplete(message, file, { files: [file], attemptId, status: 'unmeasured', reason: 'reference-unmeasured', quarantined: 'no' })
2133
2299
  : new Error(message);
@@ -2164,7 +2330,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2164
2330
  }
2165
2331
  debug.log('release-loop.gate', 'pod-isolated-recheck', { file, commit, job: job.job, rc: rc ?? 124, relaxed });
2166
2332
  }
2167
- if (warmEligible && pod.warmReplay && pod.warmQuarantine && pod.warmFinalize) {
2333
+ if (warmEligible && !pod.warmProducerEnabled && pod.warmReplay && pod.warmQuarantine && pod.warmFinalize) {
2168
2334
  shadowDeadline = Date.now() + shadowTimeoutMs;
2169
2335
  const first = results.get(files[0]!)!;
2170
2336
  const reference = first.warmReference;
@@ -2240,11 +2406,26 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2240
2406
  debug.log('release-loop.gate', 'warm-shadow-recheck', first.warmShadow);
2241
2407
  }
2242
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
+ }
2243
2419
  return results;
2420
+ } catch (error) {
2421
+ await quarantine('gate-job-incomplete');
2422
+ throw error;
2423
+ }
2244
2424
  };
2245
2425
  return {
2246
2426
  command,
2247
2427
  localCommand: commandOverride && !remote ? commandOverride : localCommand,
2428
+ now: clock,
2248
2429
  async recheckPod(file, commit, pod, timeoutMs, relaxed = false) {
2249
2430
  return (await runPodRecheckBatch([file], commit, pod, timeoutMs, relaxed)).get(file)!;
2250
2431
  },
@@ -2413,7 +2594,6 @@ export function saveBaselineFileCache(ledger: string, commit: string, rows: Base
2413
2594
 
2414
2595
  export async function judgeGate(opts: GateOptions, runner: GateRunner = createGateRunner(opts.repo ?? process.cwd(), opts.remote),
2415
2596
  imageProbe: typeof preflightGateImageDigest = preflightGateImageDigest): Promise<GateResult> {
2416
- const start = Date.now();
2417
2597
  if (opts.pod?.warmReplay) opts = { ...opts, pod: { ...opts.pod,
2418
2598
  warmShadowScope: { requested: false, attempts: 0 } } };
2419
2599
  const explicitConfig = getElanousConfigDirOverride();
@@ -2424,6 +2604,10 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2424
2604
  debug.log('release.run', opts.forceFreeze ? 'freeze-forced' : 'frozen', { version: opts.version, reason: frozen.reason, until: frozen.until, node: 'gate' });
2425
2605
  if (!opts.forceFreeze) throw new Error(landingFreezeMessage(frozen));
2426
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();
2427
2611
  const ledger = explicitConfig ? root : (opts.ledgerRoot ?? releaseLedgerRoot());
2428
2612
  const roots = [...new Set([ledger, root])];
2429
2613
  const cachedBaseline = (version: string, commit: string) => {
@@ -2561,6 +2745,9 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2561
2745
  const runCut = (selected: typeof partial) => selected?.kind === 'carry' ? undefined : runner.sweep(cutTree,
2562
2746
  join(root, 'release', opts.version, 'gate-logs', selected ? 'cut-partial' : 'cut'),
2563
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 },
2564
2751
  // GATE-LIVE-OBS: the release ledger's shards.json (UX #24782) — never the run tree's universe; an explicit
2565
2752
  // --ledger-root (tests) keeps it in that ledger.
2566
2753
  shardsFile: { path: opts.ledgerRoot ? gateShardsPath(opts.version, opts.ledgerRoot) : gateShardsPath(opts.version), version: opts.version } } : undefined,
@@ -2575,6 +2762,10 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2575
2762
  partial = null;
2576
2763
  cutRun = await runCut(null);
2577
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
+ });
2578
2769
  cutSweepRc = cutRun?.rc ?? null;
2579
2770
  cutSweepUsable = !!cutRun && (cutRun.rc === 0 || cutRun.rc === 1) && !cutRun.timedOut
2580
2771
  && !cutRun.abortedShards && !cutRun.stalledEnv?.length;
@@ -2770,7 +2961,7 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2770
2961
  debug.log('release-loop.gate', 'isolated-recheck-plan', { files: recheckFiles.length, window: recheckLimit,
2771
2962
  concurrency: opts.pod && runner.recheckPod ? recheckConcurrency : 1, executor: opts.pod && runner.recheckPod ? 'pod' : 'host',
2772
2963
  remote: opts.remote ?? null, podBatchFiles: opts.pod && runner.recheckPod && runner.recheckPodBatch ? GATE_RECHECK_POD_BATCH_FILES : 1 });
2773
- const recheckStartedAt = Date.now();
2964
+ const recheckStartedAt = clock();
2774
2965
  const recordWarmShadow = (shadow?: WarmShadowObservation) => {
2775
2966
  if (shadow && !result.warmShadow?.some((entry) => entry.attemptId === shadow.attemptId))
2776
2967
  (result.warmShadow ??= []).push(shadow);
@@ -2933,6 +3124,11 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2933
3124
  result.introduced.sort();
2934
3125
  preexistingIds.sort();
2935
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 });
2936
3132
  result.preexisting = preexistingIds.length;
2937
3133
  result.baselineCache = cacheStats;
2938
3134
  debug.log('release.gate', 'baseline-cache', { hits: cacheStats.hits, misses: cacheStats.misses, commit: baseSha });
@@ -2985,7 +3181,7 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2985
3181
  } catch (error) { result.outcome = 'error'; result.error = `cleanup: ${String(error)}`; }
2986
3182
  }
2987
3183
  if (opts.remote) runner.remoteMirror = undefined;
2988
- result.durationMs = Date.now() - start;
3184
+ result.durationMs = clock() - start;
2989
3185
  }
2990
3186
  if (cutFailures && !result.stalledShards?.length && !result.stalledEnv.length && !result.unmeasuredRecheckFiles?.length) {
2991
3187
  try {