elanous 0.2.35 → 0.2.37

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 (827) 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/{T1mBQqmkwKDLGtyisDCOd → 8kITumG9GAZ1LwT9POrAR}/_buildManifest.js +1 -1
  4. package/apps/pwa/out/_next/static/chunks/1041-5841aae848fb6403.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/1087.3177e54f672dc526.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1105.efe1e13503ac7a33.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/1224.2ceca6c180d5130b.js +62 -0
  8. package/apps/pwa/out/_next/static/chunks/{9952.5daf5f94063a2b3e.js → 1262.a89bb27a5b2f844e.js} +1 -1
  9. package/apps/pwa/out/_next/static/chunks/1417-1768233ac92f8a5d.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/1612.81bbc19af06af16c.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/1746.3563617ea64023a3.js +115 -0
  12. package/apps/pwa/out/_next/static/chunks/1766.728d42053070bb2d.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/1773.d38e945481e04125.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/1974-c12f9600da3d3c26.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/{1dc5a567.9b092bfae3d7099b.js → 1ed5e8af.cf424815c7c0efad.js} +1 -1
  16. package/apps/pwa/out/_next/static/chunks/{4705-194e64e47ca0fbdb.js → 2002-a43a3f380df873ec.js} +1 -1
  17. package/apps/pwa/out/_next/static/chunks/2061.7232dc9dc058417f.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/{13.02a07436dec15f89.js → 210.90b2cbd64926cf34.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/{9512.5a76207a730e0491.js → 2283.edd9627b7bdfb546.js} +2 -2
  20. package/apps/pwa/out/_next/static/chunks/{6866-79aa5b4e47cac6aa.js → 2318-450e75d07605b8fe.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/{938.66ad1ef0e5abae1c.js → 2375.bf364c85dbb6f085.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/244-a810d282a20f1783.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/2479-fe7e188bd9402d14.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/2514.03b60f266531a015.js +148 -0
  25. package/apps/pwa/out/_next/static/chunks/2526-a657538869888604.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/2533-6f0341ff075809e9.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/2676.e7994c695ef0f30d.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/2734.3ffdf54254fb6f99.js +27 -0
  29. package/apps/pwa/out/_next/static/chunks/2770-23252bf3a37880b9.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/2969.66b8b545139d1544.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/3128.a9210d08800ea6a8.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/3199.6a1a9b0e6cd01dc6.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/3212.12179fd3eee8cef1.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/{9214.63a510d39178e997.js → 3578.4e9f9948a9a6f2f4.js} +2 -2
  35. package/apps/pwa/out/_next/static/chunks/3760-2909ddb9279281cd.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/3825.1921b5901bf33dce.js +36 -0
  37. package/apps/pwa/out/_next/static/chunks/3994-63eb884ed0547011.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/4036.d54e55e1ebf5acdc.js +90 -0
  39. package/apps/pwa/out/_next/static/chunks/4115.5b438b99f0ae729f.js +226 -0
  40. package/apps/pwa/out/_next/static/chunks/4269.d8f9f34bb0fc6c43.js +287 -0
  41. package/apps/pwa/out/_next/static/chunks/431-4eed5bd2c5fe1719.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/{2018.a741297bce759490.js → 4313.76f232c304a15bde.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/{1165.bad13fc9151a7288.js → 4324.c99ba3e83eb1b691.js} +1 -1
  44. package/apps/pwa/out/_next/static/chunks/{3f7c4ee5.3dde54f01a816613.js → 4337e36d.f42c3b391b3d8f20.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/{7385.50a308a477bdfc21.js → 4383.18e5c54db3a22c88.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/4599-96eff3f2a9c242c1.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/{8557-e7898769499cd56f.js → 4725-34bed25720cc326b.js} +1 -1
  48. package/apps/pwa/out/_next/static/chunks/4825-a9d9d4e9f5892309.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/{5483-dd5d7697f6bdb106.js → 4833-7934c592c5569f00.js} +1 -1
  50. package/apps/pwa/out/_next/static/chunks/4879-68518f547a207aff.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/4896.f9b70285363b27b2.js +24 -0
  52. package/apps/pwa/out/_next/static/chunks/{8444.4c26b379ca2b9596.js → 4931.95cc6dbd230774ca.js} +17 -17
  53. package/apps/pwa/out/_next/static/chunks/4937-28d437999b77c9dd.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/49aa54ab.80f39398e0ed0f30.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/{3408.21d1570602f4b6c0.js → 5046.7fd5cbd859299448.js} +1 -1
  56. package/apps/pwa/out/_next/static/chunks/5056.482687319b82638e.js +104 -0
  57. package/apps/pwa/out/_next/static/chunks/506.16b83d56a437a9ee.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/5260-20593b0a25d0afbc.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/5404.cc4e9fec26fd5925.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/576.485e83e3f3dec19a.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/5832-a6be14e663f65726.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/5854-56648a9fe646b9a0.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/58edce7d.f1b0c72acf13b123.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/5925.3b1f8b135710249b.js +93 -0
  65. package/apps/pwa/out/_next/static/chunks/5952.294fc574a8625184.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/{b6705a4f-b9a9047de9eecfd0.js → 5f6af561-f90b7a67f4f75d13.js} +1 -1
  67. package/apps/pwa/out/_next/static/chunks/6168.17749aa3905fa073.js +74 -0
  68. package/apps/pwa/out/_next/static/chunks/6252.240578f10952e091.js +34 -0
  69. package/apps/pwa/out/_next/static/chunks/6416.5d50b8ec9986ca98.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/{5101.e32282718f99ed6f.js → 6680.6a693c28aae4cd19.js} +1 -1
  71. package/apps/pwa/out/_next/static/chunks/{4718-17d2af636796a330.js → 6745-5a63492933959dd9.js} +3 -3
  72. package/apps/pwa/out/_next/static/chunks/6758.e7fbf532953c0783.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/6795.0cb8a59730d99703.js +149 -0
  74. package/apps/pwa/out/_next/static/chunks/{6236.e18cced2d5ba6afb.js → 6819.be5c9c4d67f22922.js} +16 -16
  75. package/apps/pwa/out/_next/static/chunks/6877-84d47b7e85d0c41e.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/{2833-0fe47af99ed13c29.js → 6971-fb5f9dc0a2cb5d2d.js} +1 -1
  77. package/apps/pwa/out/_next/static/chunks/{da5176c6.6773b6c5994e1d99.js → 709a918c.4fd3b4e2a32cb4e3.js} +1 -1
  78. package/apps/pwa/out/_next/static/chunks/717.522b08685979b814.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/7191.e41eb8f455c7f089.js +41 -0
  80. package/apps/pwa/out/_next/static/chunks/7233.8e744b7513119409.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/7238-97c2226f80504bd8.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/7285.307963afcf7e7660.js +24 -0
  83. package/apps/pwa/out/_next/static/chunks/7299-fbe4b8c47a87614a.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/7314.df0d95930b0c3f50.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/7406.c0589fdf2ee66629.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/7424.c0fe2a595d4232b5.js +23 -0
  87. package/apps/pwa/out/_next/static/chunks/752-90e3e7daad3d8e39.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/7524-bcd3d9928c6842d9.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/7549.9a4de3bf231cfc52.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/757-4fbaf50942b8c424.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/765.76ae970ceb597b5b.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/{7644.17e37ba6f1d4226e.js → 7867.e8725745ddd1d940.js} +4 -4
  93. package/apps/pwa/out/_next/static/chunks/7893-e444a1b0809cd1e4.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/7956-33748cabb3a05562.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/{0368f25d-ad84af92fefee68f.js → 7c5ed21f-f286708e57cf88f7.js} +1 -1
  96. package/apps/pwa/out/_next/static/chunks/8030-aa66b478b53dbfca.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/8051.c88c07dacbbc5f21.js +8 -0
  98. package/apps/pwa/out/_next/static/chunks/{359-2250789a2d3499e7.js → 8080-40d781e7704d3671.js} +1 -1
  99. package/apps/pwa/out/_next/static/chunks/8149.80e402d4345e8dae.js +77 -0
  100. package/apps/pwa/out/_next/static/chunks/8205.50a5268f4e12ba0b.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/8231.a128feaa6f8b6079.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/85.4ddb1dda2ea3ffa2.js +286 -0
  103. package/apps/pwa/out/_next/static/chunks/8537.26517a09e8247beb.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/8595-e9b7081ab773a61f.js +1 -0
  105. package/apps/pwa/out/_next/static/chunks/8821-30cc65a61a60951f.js +1 -0
  106. package/apps/pwa/out/_next/static/chunks/8905.b3385aadb7b133e6.js +79 -0
  107. package/apps/pwa/out/_next/static/chunks/8983.246bb3a1ac38f50f.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/{b2f6ba4d.fb56fdbbf798df33.js → 8c58d09b.e8724a299658d86f.js} +1 -1
  109. package/apps/pwa/out/_next/static/chunks/9072.18342f5941a1f3ec.js +1 -0
  110. package/apps/pwa/out/_next/static/chunks/9123.9df04866b2d6e00f.js +1 -0
  111. package/apps/pwa/out/_next/static/chunks/{1530.0e612369b6fd7ea0.js → 9151.a2c51e93f9156dae.js} +1 -1
  112. package/apps/pwa/out/_next/static/chunks/{4441-875b97f5821ed58a.js → 9175-7d383df15a82e2be.js} +1 -1
  113. package/apps/pwa/out/_next/static/chunks/9181-bf89c07ce5b8f599.js +1 -0
  114. package/apps/pwa/out/_next/static/chunks/920-429bbc4504b45f71.js +1 -0
  115. package/apps/pwa/out/_next/static/chunks/9207.625edf45d656151c.js +1 -0
  116. package/apps/pwa/out/_next/static/chunks/9378.d2575c6ffd6dd756.js +94 -0
  117. package/apps/pwa/out/_next/static/chunks/957.db5b5c4d32a35240.js +1 -0
  118. package/apps/pwa/out/_next/static/chunks/958.53a8dcf4729e6997.js +64 -0
  119. package/apps/pwa/out/_next/static/chunks/9647.8f0cd0898c592a29.js +1 -0
  120. package/apps/pwa/out/_next/static/chunks/9665-26abb02665704c2c.js +1 -0
  121. package/apps/pwa/out/_next/static/chunks/9703.e7d81b6faef1050a.js +1 -0
  122. package/apps/pwa/out/_next/static/chunks/9882-7a9164b3c2dacbe5.js +1 -0
  123. package/apps/pwa/out/_next/static/chunks/9998.b74e033efadecd10.js +1 -0
  124. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c604fb980a634d88.js +1 -0
  125. package/apps/pwa/out/_next/static/chunks/app/approvals/page-ec2d8a4979ca9075.js +1 -0
  126. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-cb6add08ad3c7660.js +1 -0
  127. package/apps/pwa/out/_next/static/chunks/app/board/page-e2eaa321ec223fe6.js +1 -0
  128. package/apps/pwa/out/_next/static/chunks/app/botlab/page-5b8a09c475b5f844.js +1 -0
  129. package/apps/pwa/out/_next/static/chunks/app/bots/page-91a77daedfbac191.js +1 -0
  130. package/apps/pwa/out/_next/static/chunks/app/ceo/page-f37fc5e974bda39b.js +1 -0
  131. package/apps/pwa/out/_next/static/chunks/app/chat/page-91429a0b78415a94.js +1 -0
  132. package/apps/pwa/out/_next/static/chunks/app/consult/page-b95798b58263b910.js +1 -0
  133. package/apps/pwa/out/_next/static/chunks/app/control/page-763d53e175e17aa1.js +1 -0
  134. package/apps/pwa/out/_next/static/chunks/app/decisions/page-d55023204055b12f.js +1 -0
  135. package/apps/pwa/out/_next/static/chunks/app/design-check/page-89d2140d78abc810.js +1 -0
  136. package/apps/pwa/out/_next/static/chunks/app/editor/page-82dc069abfe27f55.js +1 -0
  137. package/apps/pwa/out/_next/static/chunks/app/exec/page-25d9eee7beb552c0.js +1 -0
  138. package/apps/pwa/out/_next/static/chunks/app/field/page-770b4594c713ee97.js +1 -0
  139. package/apps/pwa/out/_next/static/chunks/app/healthz/route-c604fb980a634d88.js +1 -0
  140. package/apps/pwa/out/_next/static/chunks/app/inside/page-ba8e0dcae4f83985.js +1 -0
  141. package/apps/pwa/out/_next/static/chunks/app/intake/page-5c112c6d7c33656e.js +1 -0
  142. package/apps/pwa/out/_next/static/chunks/app/layout-09788e1aa9a87db6.js +1 -0
  143. package/apps/pwa/out/_next/static/chunks/app/live/page-3337c6a1328c2674.js +1 -0
  144. package/apps/pwa/out/_next/static/chunks/app/live-run/page-89acd5d68c0bc560.js +1 -0
  145. package/apps/pwa/out/_next/static/chunks/app/loops/page-5c13e2c76371d797.js +1 -0
  146. package/apps/pwa/out/_next/static/chunks/app/market/page-653a9f18642825c6.js +1 -0
  147. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-fe986fe79b107546.js +1 -0
  148. package/apps/pwa/out/_next/static/chunks/app/morning/{page-47c5d4db587266bd.js → page-1e5da297557d2354.js} +1 -1
  149. package/apps/pwa/out/_next/static/chunks/app/not-found-91ea9f5869a40943.js +1 -0
  150. package/apps/pwa/out/_next/static/chunks/app/observatory/page-7824e21b999962e2.js +1 -0
  151. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-53d00a60cc75a922.js +1 -0
  152. package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-1f5a60f70cb4dd0c.js → page-88a1edbff62eaf1d.js} +1 -1
  153. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-4e931248b3f9574b.js +1 -0
  154. package/apps/pwa/out/_next/static/chunks/app/outputs/{page-bac7dee94e5eae4a.js → page-443364c9e4a23b31.js} +1 -1
  155. package/apps/pwa/out/_next/static/chunks/app/page-b9af2ac1c4737ce8.js +1 -0
  156. package/apps/pwa/out/_next/static/chunks/app/reflection/page-25ab9c614c14129b.js +1 -0
  157. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-dbcdcfac0a395a8a.js +1 -0
  158. package/apps/pwa/out/_next/static/chunks/app/sessions/page-bf1de6645089ec58.js +1 -0
  159. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c4201761a5e839c9.js +1 -0
  160. package/apps/pwa/out/_next/static/chunks/app/settings/page-c304f469ea5d70e4.js +1 -0
  161. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-8e5a238cf5e50c9c.js +1 -0
  162. package/apps/pwa/out/_next/static/chunks/app/setup/layout-c604fb980a634d88.js +1 -0
  163. package/apps/pwa/out/_next/static/chunks/app/setup/page-32b526069ee50b3c.js +1 -0
  164. package/apps/pwa/out/_next/static/chunks/app/share/page-fb9e639c107f5f72.js +1 -0
  165. package/apps/pwa/out/_next/static/chunks/app/showroom/page-84220c776608cedc.js +1 -0
  166. package/apps/pwa/out/_next/static/chunks/app/tasks/page-e475618cbfd5ca2b.js +1 -0
  167. package/apps/pwa/out/_next/static/chunks/app/term/{page-dc0a532f5b9196ec.js → page-ac2c1e6169cfce57.js} +1 -1
  168. package/apps/pwa/out/_next/static/chunks/app/today/page-fccd3020af2f8806.js +1 -0
  169. package/apps/pwa/out/_next/static/chunks/app/trace/page-9c0d7198ff7b5a91.js +1 -0
  170. package/apps/pwa/out/_next/static/chunks/app/vault/page-d54b1ec9b9c6430d.js +1 -0
  171. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-8c4bef1791d732ca.js +1 -0
  172. package/apps/pwa/out/_next/static/chunks/app/workflows/page-46fcc0b0bbc6fbfe.js +1 -0
  173. package/apps/pwa/out/_next/static/chunks/app/workspace/page-c1c80e345fa9d6f5.js +1 -0
  174. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-7dfda00afd172f5b.js +1 -0
  175. package/apps/pwa/out/_next/static/chunks/{d06f9160.a3d9062f95de8ad7.js → d9815f84.4918f682a45a8502.js} +1 -1
  176. package/apps/pwa/out/_next/static/chunks/{cbe38a15.5a032598e39970c6.js → de8446d3.31b73eb239fecd7b.js} +1 -1
  177. package/apps/pwa/out/_next/static/chunks/f21fd540.63ac75328926ed30.js +1 -0
  178. package/apps/pwa/out/_next/static/chunks/framework-77fdcc873519a6cd.js +1 -0
  179. package/apps/pwa/out/_next/static/chunks/main-app-d0d620d2ce235713.js +1 -0
  180. package/apps/pwa/out/_next/static/chunks/main-e0ef79c79337ba90.js +1 -0
  181. package/apps/pwa/out/_next/static/chunks/pages/_app-84b1bc71e3f9ce6a.js +1 -0
  182. package/apps/pwa/out/_next/static/chunks/pages/_error-2acb61f1363128cb.js +1 -0
  183. package/apps/pwa/out/_next/static/chunks/webpack-462ff5727ceb7824.js +1 -0
  184. package/apps/pwa/out/_next/static/css/{b5d88eba78a3d7bf.css → be9019f696217521.css} +1 -1
  185. package/apps/pwa/out/approvals/index.html +1 -1
  186. package/apps/pwa/out/approvals/index.txt +23 -23
  187. package/apps/pwa/out/autopilot/index.html +1 -1
  188. package/apps/pwa/out/autopilot/index.txt +23 -23
  189. package/apps/pwa/out/board/index.html +1 -1
  190. package/apps/pwa/out/board/index.txt +23 -23
  191. package/apps/pwa/out/botlab/index.html +1 -1
  192. package/apps/pwa/out/botlab/index.txt +22 -22
  193. package/apps/pwa/out/bots/index.html +1 -1
  194. package/apps/pwa/out/bots/index.txt +22 -22
  195. package/apps/pwa/out/ceo/index.html +1 -1
  196. package/apps/pwa/out/ceo/index.txt +23 -23
  197. package/apps/pwa/out/chat/index.html +1 -1
  198. package/apps/pwa/out/chat/index.txt +22 -22
  199. package/apps/pwa/out/consult/index.html +1 -1
  200. package/apps/pwa/out/consult/index.txt +23 -23
  201. package/apps/pwa/out/control/index.html +1 -1
  202. package/apps/pwa/out/control/index.txt +23 -23
  203. package/apps/pwa/out/decisions/index.html +1 -1
  204. package/apps/pwa/out/decisions/index.txt +22 -22
  205. package/apps/pwa/out/design-check/index.html +1 -1
  206. package/apps/pwa/out/design-check/index.txt +23 -23
  207. package/apps/pwa/out/editor/index.html +1 -1
  208. package/apps/pwa/out/editor/index.txt +23 -23
  209. package/apps/pwa/out/exec/index.html +1 -1
  210. package/apps/pwa/out/exec/index.txt +23 -23
  211. package/apps/pwa/out/field/index.html +1 -1
  212. package/apps/pwa/out/field/index.txt +23 -23
  213. package/apps/pwa/out/healthz +1 -1
  214. package/apps/pwa/out/index.html +1 -1
  215. package/apps/pwa/out/index.txt +22 -22
  216. package/apps/pwa/out/inside/index.html +1 -1
  217. package/apps/pwa/out/inside/index.txt +22 -22
  218. package/apps/pwa/out/intake/index.html +1 -1
  219. package/apps/pwa/out/intake/index.txt +23 -23
  220. package/apps/pwa/out/live/index.html +1 -1
  221. package/apps/pwa/out/live/index.txt +23 -23
  222. package/apps/pwa/out/live-run/index.html +1 -1
  223. package/apps/pwa/out/live-run/index.txt +23 -23
  224. package/apps/pwa/out/loops/index.html +1 -1
  225. package/apps/pwa/out/loops/index.txt +22 -22
  226. package/apps/pwa/out/market/index.html +1 -1
  227. package/apps/pwa/out/market/index.txt +22 -22
  228. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  229. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  230. package/apps/pwa/out/morning/index.html +1 -1
  231. package/apps/pwa/out/morning/index.txt +23 -23
  232. package/apps/pwa/out/observatory/index.html +1 -1
  233. package/apps/pwa/out/observatory/index.txt +23 -23
  234. package/apps/pwa/out/ops/checklist/index.html +1 -1
  235. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  236. package/apps/pwa/out/ops/release/index.html +1 -1
  237. package/apps/pwa/out/ops/release/index.txt +22 -22
  238. package/apps/pwa/out/ops/seats/index.html +1 -1
  239. package/apps/pwa/out/ops/seats/index.txt +22 -22
  240. package/apps/pwa/out/outputs/index.html +1 -1
  241. package/apps/pwa/out/outputs/index.txt +22 -22
  242. package/apps/pwa/out/reflection/index.html +1 -1
  243. package/apps/pwa/out/reflection/index.txt +23 -23
  244. package/apps/pwa/out/scheduler/index.html +1 -1
  245. package/apps/pwa/out/scheduler/index.txt +23 -23
  246. package/apps/pwa/out/sessions/index.html +1 -1
  247. package/apps/pwa/out/sessions/index.txt +22 -22
  248. package/apps/pwa/out/settings/devices/index.html +1 -1
  249. package/apps/pwa/out/settings/devices/index.txt +23 -23
  250. package/apps/pwa/out/settings/index.html +1 -1
  251. package/apps/pwa/out/settings/index.txt +23 -23
  252. package/apps/pwa/out/setup/done/index.html +1 -1
  253. package/apps/pwa/out/setup/done/index.txt +23 -23
  254. package/apps/pwa/out/setup/index.html +1 -1
  255. package/apps/pwa/out/setup/index.txt +23 -23
  256. package/apps/pwa/out/share/index.html +1 -1
  257. package/apps/pwa/out/share/index.txt +23 -23
  258. package/apps/pwa/out/showroom/index.html +1 -1
  259. package/apps/pwa/out/showroom/index.txt +22 -22
  260. package/apps/pwa/out/tasks/index.html +1 -1
  261. package/apps/pwa/out/tasks/index.txt +23 -23
  262. package/apps/pwa/out/term/index.html +1 -1
  263. package/apps/pwa/out/term/index.txt +23 -23
  264. package/apps/pwa/out/today/index.html +1 -1
  265. package/apps/pwa/out/today/index.txt +23 -23
  266. package/apps/pwa/out/trace/index.html +1 -1
  267. package/apps/pwa/out/trace/index.txt +23 -23
  268. package/apps/pwa/out/vault/index.html +1 -1
  269. package/apps/pwa/out/vault/index.txt +23 -23
  270. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  271. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  272. package/apps/pwa/out/workflows/index.html +1 -1
  273. package/apps/pwa/out/workflows/index.txt +23 -23
  274. package/apps/pwa/out/workspace/index.html +1 -1
  275. package/apps/pwa/out/workspace/index.txt +23 -23
  276. package/apps/pwa/out/worktrees/index.html +1 -1
  277. package/apps/pwa/out/worktrees/index.txt +23 -23
  278. package/docs/goal-context/05-authoring-lessons.jsonl +47 -0
  279. package/docs/goal-context/README.md +3 -0
  280. package/graphs/default-loop.yaml +6 -1
  281. package/graphs/drafts/recipes.yaml +27 -7
  282. package/graphs/implement-loop.yaml +24 -0
  283. package/graphs/release/recipes.yaml +6 -0
  284. package/graphs/release/release-loop.yaml +17 -4
  285. package/graphs/wizard/recipes.yaml +44 -0
  286. package/graphs/wizard/wizard-loop.yaml +44 -0
  287. package/graphs/work/draft-close.yaml +54 -0
  288. package/graphs/work/green-close.yaml +37 -0
  289. package/graphs/work/recipes.yaml +46 -1
  290. package/graphs/work/research-report.yaml +2 -0
  291. package/graphs/work/run-research.yaml +35 -0
  292. package/package.json +1 -1
  293. package/scripts/__fixtures__/authoring-paths-10x3.json +32 -0
  294. package/scripts/boundary-proof-mapping-audit.ts +88 -0
  295. package/scripts/ci-typecheck-changed.ts +139 -5
  296. package/scripts/codex-fallback-live-probe.ts +17 -15
  297. package/scripts/coord-role-pack.ts +1 -1
  298. package/scripts/first10-gate.ts +416 -0
  299. package/scripts/fleet/node-setup.sh +32 -2
  300. package/scripts/fleet/run-light-worker.sh +9 -0
  301. package/scripts/fleet/wsl-node-setup.sh +432 -0
  302. package/scripts/gate-0226-failure-census.ts +507 -0
  303. package/scripts/goals/authoring-metrics.ts +258 -0
  304. package/scripts/graph-units/draft-close-node.ts +222 -0
  305. package/scripts/hook-order-sweep.ts +7 -4
  306. package/scripts/land-global-checks.ts +69 -1
  307. package/scripts/measure-nl-routing-corpus.ts +207 -2
  308. package/scripts/release-loop/docs-node.ts +344 -8
  309. package/scripts/release-loop/gate-closure.ts +48 -0
  310. package/scripts/release-loop/gate-diff.ts +2 -1
  311. package/scripts/release-loop/gate-node.ts +249 -53
  312. package/scripts/release-loop/known-issues-node.ts +6 -4
  313. package/scripts/release-loop/node-verdict.ts +2 -0
  314. package/scripts/release-loop/notes-repair.ts +216 -0
  315. package/scripts/release-loop/prepare-node.ts +61 -5
  316. package/scripts/release-loop/resume-cap.ts +14 -34
  317. package/scripts/release-loop/resume-release.ts +151 -15
  318. package/scripts/release-loop/resume-test-traffic.ts +10 -0
  319. package/scripts/release-loop/traffic-node.ts +59 -0
  320. package/scripts/release-loop/traffic-transition.ts +159 -0
  321. package/scripts/release-loop/version-node.ts +81 -12
  322. package/scripts/rhythm/daily.ts +137 -3
  323. package/scripts/train/green-leg.ts +176 -0
  324. package/scripts/tui-continue-real-evidence.ts +47 -0
  325. package/scripts/ux-sim.ts +53 -0
  326. package/skills/README.md +1 -0
  327. package/src/acp/client.ts +31 -5
  328. package/src/acp/core-turn-bridge.ts +54 -6
  329. package/src/acp/stderr-summary.ts +49 -0
  330. package/src/agent/delegate-hint.ts +16 -0
  331. package/src/agent/monad-agent-turn.ts +1 -0
  332. package/src/agent/runner.ts +82 -2
  333. package/src/agent/task-notification.ts +56 -10
  334. package/src/agent/terminal-surface.ts +217 -0
  335. package/src/agent/types.ts +4 -0
  336. package/src/agent-substrate/pr-reviewer.ts +64 -4
  337. package/src/agent-substrate/review-intent.ts +29 -2
  338. package/src/agent-substrate/review-observation.ts +3 -0
  339. package/src/agent-substrate/self-review-cli.ts +490 -26
  340. package/src/autopilot/lineage/sources.ts +1 -1
  341. package/src/autopilot/mission-codebase-gate.ts +141 -12
  342. package/src/autopilot/pr-manager.ts +89 -18
  343. package/src/boot/daemon-runtime.ts +35 -3
  344. package/src/boot/daemon-tools/pty-shell.ts +2 -1
  345. package/src/boot/daemon-tools/self-implement.ts +1 -0
  346. package/src/budget/run-usage-rollup.ts +12 -7
  347. package/src/budget/unified-usage.ts +20 -0
  348. package/src/budget/usage-outcomes.ts +8 -4
  349. package/src/capture/tui-self-report.ts +132 -1
  350. package/src/chat/goal-continue-verdict.ts +41 -0
  351. package/src/chat/index.ts +30 -4
  352. package/src/chat/internal-turn-origin.ts +1 -0
  353. package/src/chat/paste-fold.ts +86 -0
  354. package/src/chat/tool-render/block.ts +32 -0
  355. package/src/chat/tool-render/dispatch.ts +2 -20
  356. package/src/chat/tool-render/result-text.ts +98 -0
  357. package/src/chat/turn-typeahead.ts +70 -7
  358. package/src/cli/autopilot-cli.ts +24 -9
  359. package/src/cli/config-audit.ts +119 -0
  360. package/src/cli/decisions-cli.ts +7 -4
  361. package/src/cli/doctor-cli.ts +137 -6
  362. package/src/cli/guardian-cli.ts +10 -0
  363. package/src/cli/herdr-cli.ts +34 -0
  364. package/src/cli/light-entry-route.ts +6 -3
  365. package/src/cli/log-event-names.ts +2 -0
  366. package/src/cli/logs-cli.ts +357 -2
  367. package/src/cli/make-cli.ts +247 -11
  368. package/src/cli/memory-cli.ts +30 -1
  369. package/src/cli/msg-cli.ts +132 -14
  370. package/src/cli/pr-cli.ts +29 -16
  371. package/src/cli/pr-granularity.ts +5 -3
  372. package/src/cli/pr-land-base-read.ts +11 -2
  373. package/src/cli/pr-land-heavy-remote.ts +6 -3
  374. package/src/cli/pr-land-overlap-evidence.ts +23 -4
  375. package/src/cli/pr-land-recovery.ts +117 -1
  376. package/src/cli/pty-takeover-cli.ts +8 -7
  377. package/src/cli/release-checklist-read-cli.ts +109 -4
  378. package/src/cli/release-cli.ts +64 -15
  379. package/src/cli/release-single-flight.ts +1 -1
  380. package/src/cli/self-update.ts +23 -5
  381. package/src/cli/self-utterances-cli.ts +13 -16
  382. package/src/cli/tasks-card-show.ts +162 -0
  383. package/src/cli/tasks-cli.ts +868 -352
  384. package/src/cli/tasks-read-cli.ts +197 -0
  385. package/src/cli/usage-cli.ts +12 -10
  386. package/src/context-bus/claude-memory-import.ts +99 -0
  387. package/src/context-bus/long-term-memory.ts +125 -0
  388. package/src/core-turn/run-core-turn.ts +4 -2
  389. package/src/core-turn/run-goal-loop.ts +76 -16
  390. package/src/core-turn/types.ts +8 -1
  391. package/src/dashboard/chat/acp-chat.ts +9 -0
  392. package/src/dashboard/context/keys.ts +8 -31
  393. package/src/dashboard/index.ts +299 -62
  394. package/src/dashboard/input/agent-wake.ts +50 -1
  395. package/src/dashboard/input/chat-main-acp-dispatch.ts +21 -1
  396. package/src/dashboard/input/chat-main-plain-turn-runtime.ts +146 -3
  397. package/src/dashboard/input/chat-main-slash-command.ts +4 -2
  398. package/src/dashboard/input/goal-continue.ts +210 -0
  399. package/src/dashboard/input/goal-interim.ts +2 -0
  400. package/src/dashboard/input/internal-turn.ts +2 -8
  401. package/src/dashboard/input/seat-autowake.ts +9 -7
  402. package/src/dashboard/input/turn-autoheal.ts +52 -0
  403. package/src/dashboard/replay-preview.ts +25 -0
  404. package/src/dashboard/seat-boot-preamble.ts +132 -5
  405. package/src/dashboard/slash-runtime/chat-persona.ts +65 -8
  406. package/src/dashboard/slash-runtime/dashboard-handlers.ts +20 -10
  407. package/src/dashboard/slash-runtime/model-choices.ts +15 -0
  408. package/src/dashboard/stale-install-check.ts +3 -99
  409. package/src/dashboard/synchronized-frame-output.ts +74 -0
  410. package/src/debug/batch-queue.ts +119 -0
  411. package/src/debug/tui-log-tag.ts +67 -0
  412. package/src/display/agent-surface.ts +7 -0
  413. package/src/display/coordinator.ts +45 -12
  414. package/src/display/resize-listener.ts +41 -20
  415. package/src/display/synchronized-output-region.ts +60 -0
  416. package/src/domains/standalone-log-sink.ts +15 -2
  417. package/src/domains/surface-events.ts +80 -1
  418. package/src/esc-abort-gate.ts +69 -0
  419. package/src/exec-requests/planner.ts +37 -2
  420. package/src/execution-loop/launch-gate.ts +4 -3
  421. package/src/git-fs/cli-stderr.ts +45 -0
  422. package/src/git-fs/gh-cli.ts +6 -3
  423. package/src/git-fs/git-cli.ts +11 -5
  424. package/src/git-fs/worktree.ts +95 -1
  425. package/src/graph-runner/graph-cli.ts +44 -2
  426. package/src/graph-runner/graph-run-api.ts +3 -2
  427. package/src/graph-runner/runner.ts +37 -16
  428. package/src/graph-wizard/generate.ts +131 -4
  429. package/src/graph-wizard/steps.ts +15 -0
  430. package/src/grounding/proactive-ground.ts +86 -0
  431. package/src/harness/control-inbox.ts +49 -1
  432. package/src/harness/harness-cli-command.ts +230 -40
  433. package/src/harness/harness-incidents.ts +52 -1
  434. package/src/harness/pod-control-send.ts +33 -5
  435. package/src/harness/review-adapter.ts +26 -6
  436. package/src/harness/self-send-remote.ts +69 -0
  437. package/src/harness/self-send-routing-guard.ts +6 -0
  438. package/src/harness/self-send-target.ts +4 -3
  439. package/src/index.ts +443 -25
  440. package/src/input-core/context-key-runtime.ts +21 -0
  441. package/src/instance/test-write-guard-real-transport.fixture.ts +52 -0
  442. package/src/light-cli.ts +6 -1
  443. package/src/llm/route-decision.ts +13 -0
  444. package/src/llm-tool-name.ts +37 -0
  445. package/src/llm-tool-schema.ts +35 -0
  446. package/src/llm-wellformed.ts +70 -0
  447. package/src/llm.ts +158 -15
  448. package/src/loops/checker.ts +100 -0
  449. package/src/loops/consensus.ts +26 -2
  450. package/src/loops/loop-cli.ts +9 -4
  451. package/src/loops/msg-wake.ts +35 -39
  452. package/src/loops/orchestrator/anomaly-cluster.ts +243 -0
  453. package/src/loops/orchestrator/inbox.ts +59 -11
  454. package/src/loops/orchestrator/knowhow-pack.ts +364 -0
  455. package/src/loops/orchestrator/tick.ts +54 -5
  456. package/src/maturity/feature-maturity.ts +1 -1
  457. package/src/model-tier/llm-tier-map.ts +12 -17
  458. package/src/models/prompts.ts +17 -2
  459. package/src/msg/msg-post-office.ts +116 -0
  460. package/src/msg/msg-store.ts +17 -0
  461. package/src/mss/logging/log-store.ts +79 -22
  462. package/src/native-tool-catalog.ts +1 -1
  463. package/src/nexus/api/decisions-route.ts +38 -18
  464. package/src/nexus/api/graph-wizard-api.ts +46 -3
  465. package/src/nexus/api/grid.ts +24 -4
  466. package/src/nexus/api/http-server.ts +1 -1
  467. package/src/nexus/api/ops-api.ts +14 -6
  468. package/src/nexus/boot/register-mcp-clients.ts +263 -38
  469. package/src/nexus/onboarding/first-boot-wizard.ts +56 -8
  470. package/src/nexus/state/state.ts +7 -1
  471. package/src/oauth/codex-account-store.ts +34 -1
  472. package/src/onboarding/first-run.ts +37 -6
  473. package/src/onboarding/web-first.ts +43 -11
  474. package/src/ops/loops-map.ts +46 -0
  475. package/src/ops/overview-cache.ts +22 -4
  476. package/src/ops/overview-collect.ts +12 -6
  477. package/src/ops/overview.ts +44 -8
  478. package/src/persona/loader.ts +11 -0
  479. package/src/persona/types.ts +8 -0
  480. package/src/policy/llm-policy.ts +3 -1
  481. package/src/prompt-enhance/enhance.ts +4 -4
  482. package/src/prompt-library/session-guidance.ts +14 -0
  483. package/src/provider-summary.ts +8 -0
  484. package/src/pty-shell/availability.ts +7 -0
  485. package/src/pty-shell/herdr-agent-report.ts +155 -37
  486. package/src/pty-shell/herdr-prompt.ts +266 -0
  487. package/src/pty-shell/herdr-prune-stale.ts +17 -0
  488. package/src/pty-shell/herdr-sequence-lease.ts +60 -0
  489. package/src/pty-shell/pty-control-ipc.ts +327 -15
  490. package/src/pty-shell/pty-special-keys.ts +42 -2
  491. package/src/pty-shell/registry.ts +25 -15
  492. package/src/pty-shell/stale-install-check.ts +99 -0
  493. package/src/pty-shell/tui-run-state.ts +249 -3
  494. package/src/release-loop/auto-start.ts +20 -2
  495. package/src/release-loop/checklist.ts +126 -9
  496. package/src/release-loop/feature-store.ts +9 -9
  497. package/src/release-loop/landed-but-yellow.ts +93 -12
  498. package/src/release-loop/rehearsal.ts +36 -20
  499. package/src/repl/index.ts +27 -1
  500. package/src/seat-loop/knowhow-lint.ts +1 -1
  501. package/src/seat-loop/seat-report.ts +1 -0
  502. package/src/self-dev/ask-launch-flow.ts +86 -10
  503. package/src/self-dev/decompose-proposal.ts +183 -20
  504. package/src/self-dev/decompose.ts +61 -0
  505. package/src/self-dev/dev-cli.ts +37 -3
  506. package/src/self-dev/dev-pipeline.ts +37 -9
  507. package/src/self-dev/draft-sweep.ts +241 -16
  508. package/src/self-dev/draft-triage-rules.ts +50 -5
  509. package/src/self-dev/glm-credential-guard.ts +77 -0
  510. package/src/self-dev/graph-journey-nodes.ts +1 -0
  511. package/src/self-dev/graph-journey-route.ts +12 -2
  512. package/src/self-dev/orchestrate-cli.ts +68 -3
  513. package/src/self-dev/orchestrate.ts +116 -17
  514. package/src/self-dev/review-provider-fallback.ts +45 -7
  515. package/src/self-dev/run-supervisor.ts +55 -2
  516. package/src/self-dev/run-types.ts +1 -1
  517. package/src/self-dev/shard-plan.ts +48 -0
  518. package/src/self-dev/shard-run.ts +50 -3
  519. package/src/self-health/autoheal.ts +153 -0
  520. package/src/self-health/signatures.ts +6 -0
  521. package/src/self-implement/device-first.ts +116 -0
  522. package/src/self-implement/doctor-visit.ts +322 -1
  523. package/src/self-implement/evidence-command-budget.ts +80 -0
  524. package/src/self-implement/gate-artifacts.ts +6 -2
  525. package/src/self-implement/gate-baseline.ts +64 -1
  526. package/src/self-implement/gate-cli.ts +93 -9
  527. package/src/self-implement/gate-remote.ts +28 -4
  528. package/src/self-implement/gate-scope.ts +13 -1
  529. package/src/self-implement/goal-author-cli.ts +1 -0
  530. package/src/self-implement/goal-author-decompose.ts +265 -4
  531. package/src/self-implement/goal-author.ts +366 -26
  532. package/src/self-implement/goal-elaborate.ts +176 -22
  533. package/src/self-implement/goal-refine.ts +3 -0
  534. package/src/self-implement/goal-supervisor-fill.ts +75 -26
  535. package/src/self-implement/graph-variant.ts +40 -6
  536. package/src/self-implement/headless-elanous-driver.ts +19 -3
  537. package/src/self-implement/importer-test-index.ts +215 -2
  538. package/src/self-implement/knowhow-pack.ts +5 -5
  539. package/src/self-implement/off-diff-evidence.ts +58 -6
  540. package/src/self-implement/orchestrator.ts +536 -42
  541. package/src/self-implement/pod-warm-pool-profile.ts +1 -0
  542. package/src/self-implement/pwa-import-graph.ts +1 -1
  543. package/src/self-implement/review-round-brief.ts +300 -0
  544. package/src/self-implement/rework-policy.ts +44 -0
  545. package/src/self-implement/run-ledger.ts +278 -6
  546. package/src/self-implement/run-stop.ts +1 -1
  547. package/src/self-implement/run-ttl.ts +38 -24
  548. package/src/self-implement/running-runs.ts +17 -3
  549. package/src/self-implement/scope-adjacency.ts +3 -1
  550. package/src/self-implement/seams.ts +72 -10
  551. package/src/self-implement/stop-autoheal.ts +1 -0
  552. package/src/self-implement/test-seams.ts +13 -1
  553. package/src/self-implement/train-reach-grouping.ts +8 -2
  554. package/src/self-implement/wiring-check.ts +358 -0
  555. package/src/session/chat.ts +7 -1
  556. package/src/session/id-prefix.ts +60 -0
  557. package/src/session/index.ts +4 -0
  558. package/src/session-runtime/index.ts +9 -1
  559. package/src/shell-env-bootstrap.ts +11 -4
  560. package/src/skills/runner.ts +4 -2
  561. package/src/skills/tools/agent.ts +290 -48
  562. package/src/skills/tools/pty.ts +5 -0
  563. package/src/skills/tools/schedule-wakeup.ts +23 -4
  564. package/src/skills/tools/shell.ts +61 -6
  565. package/src/skills/tools/terminal-modal-inject.ts +1 -2
  566. package/src/skills/tools/terminal-modal.ts +10 -8
  567. package/src/skills/url-router.ts +41 -1
  568. package/src/smart-wizard/run.ts +66 -0
  569. package/src/status/bar.ts +34 -6
  570. package/src/status/metrics.ts +12 -1
  571. package/src/status/status-provider-info.ts +37 -6
  572. package/src/steward/rejudge-tick.ts +22 -7
  573. package/src/steward/resync-tick.ts +31 -4
  574. package/src/steward/ta-repair-event-collect.ts +17 -6
  575. package/src/task-agent/actions.ts +202 -5
  576. package/src/task-agent/boundary-proof-mapping.ts +12 -0
  577. package/src/task-agent/boundary-proof-producer.ts +147 -0
  578. package/src/task-agent/completion-evidence.ts +2 -1
  579. package/src/task-agent/green-close-recipe.ts +239 -0
  580. package/src/task-agent/green-sweep.ts +64 -10
  581. package/src/task-agent/land-queue.ts +21 -8
  582. package/src/task-agent/live-moves.ts +70 -9
  583. package/src/task-agent/map-host-adapter.ts +134 -0
  584. package/src/task-agent/map-reduce.ts +158 -0
  585. package/src/task-agent/map-run-ledger.ts +226 -0
  586. package/src/task-agent/mission.ts +30 -0
  587. package/src/task-agent/parent-host.ts +21 -11
  588. package/src/task-agent/rejudge-on-head.ts +87 -5
  589. package/src/task-agent/repair-cycle.ts +179 -32
  590. package/src/task-agent/repair-next-move.ts +42 -4
  591. package/src/task-agent/repair-outcomes.ts +29 -0
  592. package/src/task-agent/repair-worker.ts +14 -4
  593. package/src/task-agent/review-prior-fix.ts +12 -2
  594. package/src/task-agent/shadow.ts +229 -8
  595. package/src/task-agent/task-card-prefix.ts +2 -0
  596. package/src/task-agent/task-hand.ts +630 -30
  597. package/src/task-agent/task-parent-role-llm.ts +45 -0
  598. package/src/task-agent/tier-split.ts +80 -10
  599. package/src/task-agent/work-order.ts +53 -2
  600. package/src/task-orchestrator/dispatcher.ts +46 -1
  601. package/src/task-orchestrator/generator-prompt.ts +6 -0
  602. package/src/task-orchestrator/surfaces/pod-account-broker.ts +9 -0
  603. package/src/task-orchestrator/surfaces/pod-artifact-return.ts +16 -6
  604. package/src/task-orchestrator/surfaces/pod-child-reuse.ts +146 -0
  605. package/src/task-orchestrator/surfaces/pod-command-job.ts +86 -5
  606. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +215 -3
  607. package/src/task-orchestrator/surfaces/pod-launch-history.ts +133 -0
  608. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +8 -6
  609. package/src/task-orchestrator/surfaces/pod-ledger-events.ts +97 -0
  610. package/src/task-orchestrator/surfaces/pod-manifest-test-normalize.ts +11 -0
  611. package/src/task-orchestrator/surfaces/pod-pool.ts +126 -8
  612. package/src/task-orchestrator/surfaces/pod-provenance.ts +8 -2
  613. package/src/task-orchestrator/surfaces/pod-repair-commit.ts +18 -3
  614. package/src/task-orchestrator/surfaces/pod-si-warm-entry.ts +16 -0
  615. package/src/task-orchestrator/surfaces/pod-si-warm-host.ts +224 -0
  616. package/src/task-orchestrator/surfaces/pod-si-warm-protocol.ts +201 -0
  617. package/src/task-orchestrator/surfaces/pod-si-warm-provider.ts +444 -0
  618. package/src/task-orchestrator/surfaces/pod-si-warm-runner.ts +387 -0
  619. package/src/task-orchestrator/surfaces/pod-si-warm-transport.ts +157 -0
  620. package/src/task-orchestrator/surfaces/pod-warm-worker-protocol.ts +4 -0
  621. package/src/task-orchestrator/surfaces/pod-warm-worker-provider.ts +491 -36
  622. package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +3 -0
  623. package/src/task-orchestrator/surfaces/self-implement-pod.ts +612 -54
  624. package/src/task-orchestrator/surfaces/self-implement.ts +46 -2
  625. package/src/testing/linux-env.ts +177 -0
  626. package/src/tool-runtime/index.ts +15 -2
  627. package/src/tool-runtime/mcp-proxy-bootstrap.ts +29 -0
  628. package/src/tty-foreground.ts +155 -0
  629. package/src/tui/chat/goal-interim.ts +177 -0
  630. package/src/tui/chat/log-view.ts +205 -18
  631. package/src/tui/chat/self-health-status.ts +232 -0
  632. package/src/tui/entry.ts +623 -80
  633. package/src/tui/input/at-picker.ts +55 -0
  634. package/src/tui/input/composer.ts +8 -1
  635. package/src/tui/slash/compact-command.ts +7 -0
  636. package/src/tui/slash/compact.ts +1 -7
  637. package/src/tui/slash/resume.ts +98 -0
  638. package/src/tui/slash/signals.ts +114 -0
  639. package/src/tui/slash/term-next.ts +71 -0
  640. package/src/tui-client/local-chat-turn.ts +49 -5
  641. package/src/tui-client/next-dashboard-runtime.ts +8 -0
  642. package/src/tui-client/next-loop-command.ts +70 -0
  643. package/src/tui-client/next-wakeup.ts +191 -0
  644. package/src/tui.ts +37 -0
  645. package/src/ui/modal-adapter.ts +2 -2
  646. package/src/user-config.ts +19 -1
  647. package/src/ux-sim/boot-daemon-tools-pty-shell-device.ts +136 -0
  648. package/src/ux-sim/classic-tui-seat-scenarios.ts +670 -0
  649. package/src/ux-sim/cli-config-audit-device.ts +121 -0
  650. package/src/ux-sim/dashboard-event-loop-stall-monitor-device.ts +238 -0
  651. package/src/ux-sim/event-monkey.ts +3 -2
  652. package/src/ux-sim/fake-bin.ts +45 -0
  653. package/src/ux-sim/fake-gh.ts +174 -0
  654. package/src/ux-sim/fake-herdr.ts +238 -0
  655. package/src/ux-sim/fake-kubectl.ts +142 -0
  656. package/src/ux-sim/fake-llm.ts +116 -6
  657. package/src/ux-sim/fault-glm-no-credential.ts +116 -0
  658. package/src/ux-sim/fault-nextmove-dispatch.ts +102 -1
  659. package/src/ux-sim/fault-scope-escape-lift.ts +33 -6
  660. package/src/ux-sim/fault-tui-400-autoheal.ts +297 -0
  661. package/src/ux-sim/goal-device.ts +619 -0
  662. package/src/ux-sim/in-process-cli.ts +22 -5
  663. package/src/ux-sim/mock-authoring-first-run-child.ts +35 -0
  664. package/src/ux-sim/mock-authoring-first-run.ts +97 -0
  665. package/src/ux-sim/mock-seat-pty-frame-child.ts +29 -0
  666. package/src/ux-sim/mock-seat-pty-frame.ts +297 -0
  667. package/src/ux-sim/mock-seat-pty-sequence-child.ts +21 -0
  668. package/src/ux-sim/mock-seat-pty-sequence.ts +206 -0
  669. package/src/ux-sim/next-tui-seat-scenarios.ts +749 -0
  670. package/src/ux-sim/onboarding-first-run-device.ts +269 -0
  671. package/src/ux-sim/pty-key-modifiers-device.ts +85 -0
  672. package/src/ux-sim/resume-picker-device.ts +173 -0
  673. package/src/ux-sim/session-id-seed.ts +60 -0
  674. package/src/ux-sim/skills-tools-shell-device.ts +156 -0
  675. package/src/ux-sim/subagent-tools-device.ts +81 -0
  676. package/src/ux-sim/task-agent-parent-host-device.ts +264 -0
  677. package/src/ux-sim/task-agent-rejudge-on-head-device.ts +114 -0
  678. package/src/ux-sim/task-orchestrator-surfaces-self-implement-pod-device.ts +250 -0
  679. package/src/ux-sim/tty-foreground-steal-child.ts +21 -0
  680. package/src/version/packed-revision.json +1 -1
  681. package/apps/pwa/out/_next/static/chunks/1540.af38731fa7c4a3e0.js +0 -41
  682. package/apps/pwa/out/_next/static/chunks/1674.2a65fd5668e1cc5f.js +0 -1
  683. package/apps/pwa/out/_next/static/chunks/1850.f2b470ba5a471da0.js +0 -24
  684. package/apps/pwa/out/_next/static/chunks/193.62da94cd0b88323b.js +0 -74
  685. package/apps/pwa/out/_next/static/chunks/1975.260f6d249fac5f7c.js +0 -1
  686. package/apps/pwa/out/_next/static/chunks/1982-67cd8dfe8682325f.js +0 -1
  687. package/apps/pwa/out/_next/static/chunks/1993-c0007b7ad0c0ef41.js +0 -1
  688. package/apps/pwa/out/_next/static/chunks/1999-bb415351c8a2846c.js +0 -1
  689. package/apps/pwa/out/_next/static/chunks/1c735de4.f993a30bbd3c57c4.js +0 -1
  690. package/apps/pwa/out/_next/static/chunks/2085.8f950b4bda1cbbc7.js +0 -1
  691. package/apps/pwa/out/_next/static/chunks/2159-6179a961376956b9.js +0 -1
  692. package/apps/pwa/out/_next/static/chunks/216.b5c2a421aa1d28a5.js +0 -1
  693. package/apps/pwa/out/_next/static/chunks/2251.49fbe369f74c372c.js +0 -148
  694. package/apps/pwa/out/_next/static/chunks/2286-2939577360d3dcc0.js +0 -1
  695. package/apps/pwa/out/_next/static/chunks/2625.5d870aa999f33db1.js +0 -34
  696. package/apps/pwa/out/_next/static/chunks/2668.34d21539e62bf111.js +0 -77
  697. package/apps/pwa/out/_next/static/chunks/2930.d0edb4f108787ef7.js +0 -1
  698. package/apps/pwa/out/_next/static/chunks/2973.77318e7d85f59477.js +0 -62
  699. package/apps/pwa/out/_next/static/chunks/3103.0df8da390948bb84.js +0 -1
  700. package/apps/pwa/out/_next/static/chunks/3123.2bee9ba15d320bd2.js +0 -90
  701. package/apps/pwa/out/_next/static/chunks/3162-6cf652bf7124d8cf.js +0 -1
  702. package/apps/pwa/out/_next/static/chunks/3577-7340009f7007a795.js +0 -1
  703. package/apps/pwa/out/_next/static/chunks/3589.5444a06bcf6e775c.js +0 -1
  704. package/apps/pwa/out/_next/static/chunks/3618.f514792d6c59d7fd.js +0 -36
  705. package/apps/pwa/out/_next/static/chunks/3669.343f9119aa276001.js +0 -1
  706. package/apps/pwa/out/_next/static/chunks/3677-4215f826f6aa68e5.js +0 -1
  707. package/apps/pwa/out/_next/static/chunks/3736.c2efad489604cc3b.js +0 -79
  708. package/apps/pwa/out/_next/static/chunks/3817.f9aeab2bb6675d73.js +0 -1
  709. package/apps/pwa/out/_next/static/chunks/4182-0b9b1251c29cc0cf.js +0 -1
  710. package/apps/pwa/out/_next/static/chunks/4346.8601952e73bf5803.js +0 -1
  711. package/apps/pwa/out/_next/static/chunks/4582.bd68683c076649e3.js +0 -286
  712. package/apps/pwa/out/_next/static/chunks/4605-6e623f55aec77422.js +0 -1
  713. package/apps/pwa/out/_next/static/chunks/4607.5cc27468628eb4bf.js +0 -1
  714. package/apps/pwa/out/_next/static/chunks/4627.f07508b3936fd6c0.js +0 -24
  715. package/apps/pwa/out/_next/static/chunks/4747-c22262c093e46807.js +0 -1
  716. package/apps/pwa/out/_next/static/chunks/4775-178215aed0daba61.js +0 -1
  717. package/apps/pwa/out/_next/static/chunks/4815-6ffb8c3c991cc45d.js +0 -1
  718. package/apps/pwa/out/_next/static/chunks/4834-beca8b47ad29a898.js +0 -1
  719. package/apps/pwa/out/_next/static/chunks/5023-fb4802b6d2492b02.js +0 -1
  720. package/apps/pwa/out/_next/static/chunks/5154.a118f3957ed14e36.js +0 -1
  721. package/apps/pwa/out/_next/static/chunks/5210-a2e0015e3d4d5d9c.js +0 -1
  722. package/apps/pwa/out/_next/static/chunks/5356.84ac712cc15928bf.js +0 -1
  723. package/apps/pwa/out/_next/static/chunks/5401.22352141b6fd336e.js +0 -23
  724. package/apps/pwa/out/_next/static/chunks/5637.281e5dec4ea6ecf1.js +0 -94
  725. package/apps/pwa/out/_next/static/chunks/5662.02eaa029af05ad46.js +0 -1
  726. package/apps/pwa/out/_next/static/chunks/56d372f2.681ab4ec4c998fae.js +0 -1
  727. package/apps/pwa/out/_next/static/chunks/5760.22456f433c6f42c0.js +0 -1
  728. package/apps/pwa/out/_next/static/chunks/5852.db1f7449f4f74612.js +0 -287
  729. package/apps/pwa/out/_next/static/chunks/5891-0f11a7d7362a9072.js +0 -1
  730. package/apps/pwa/out/_next/static/chunks/5943.264d14a44ca32513.js +0 -1
  731. package/apps/pwa/out/_next/static/chunks/604-1b3c39df083c02aa.js +0 -1
  732. package/apps/pwa/out/_next/static/chunks/6121-5f97072330738caf.js +0 -1
  733. package/apps/pwa/out/_next/static/chunks/6193.07d8ca5af4b09086.js +0 -1
  734. package/apps/pwa/out/_next/static/chunks/6203-b3fea3a60d69ff14.js +0 -1
  735. package/apps/pwa/out/_next/static/chunks/6286.d638fb5953e2e757.js +0 -8
  736. package/apps/pwa/out/_next/static/chunks/630.fe5a46428d63b013.js +0 -1
  737. package/apps/pwa/out/_next/static/chunks/6301.ae0245f507e4395e.js +0 -1
  738. package/apps/pwa/out/_next/static/chunks/6673.fbbe5699f6ea5991.js +0 -27
  739. package/apps/pwa/out/_next/static/chunks/6777-f6d301efb5de828d.js +0 -1
  740. package/apps/pwa/out/_next/static/chunks/6786.423f24c88de034c7.js +0 -93
  741. package/apps/pwa/out/_next/static/chunks/6854-cef64a948c5e122d.js +0 -1
  742. package/apps/pwa/out/_next/static/chunks/6b89c8bd.24df687a820500b3.js +0 -1
  743. package/apps/pwa/out/_next/static/chunks/7037-fe27f001db30d323.js +0 -1
  744. package/apps/pwa/out/_next/static/chunks/7080.2d826860534fd88e.js +0 -1
  745. package/apps/pwa/out/_next/static/chunks/7118.8731be28c3ebb14b.js +0 -1
  746. package/apps/pwa/out/_next/static/chunks/7196.44241bc1df3e26fe.js +0 -149
  747. package/apps/pwa/out/_next/static/chunks/7235.c7c4e6cb5c693c2d.js +0 -104
  748. package/apps/pwa/out/_next/static/chunks/7706-58bfb1546637f217.js +0 -1
  749. package/apps/pwa/out/_next/static/chunks/7839.5b91455338c9fd3f.js +0 -1
  750. package/apps/pwa/out/_next/static/chunks/7857-31f13ee96f4011aa.js +0 -1
  751. package/apps/pwa/out/_next/static/chunks/81.f74cb60312fe4001.js +0 -1
  752. package/apps/pwa/out/_next/static/chunks/8219.baa715d1deee80e2.js +0 -1
  753. package/apps/pwa/out/_next/static/chunks/8256.e3ac8421a71f82f4.js +0 -1
  754. package/apps/pwa/out/_next/static/chunks/8310-444a6a895c402f12.js +0 -1
  755. package/apps/pwa/out/_next/static/chunks/8423.f7dda8aca4fcaae5.js +0 -1
  756. package/apps/pwa/out/_next/static/chunks/8437-8d5492094b166cfa.js +0 -1
  757. package/apps/pwa/out/_next/static/chunks/8496.75649af8652c74f4.js +0 -226
  758. package/apps/pwa/out/_next/static/chunks/8576.65bb654b524f3614.js +0 -1
  759. package/apps/pwa/out/_next/static/chunks/8609.558ce2f8f9aea2be.js +0 -64
  760. package/apps/pwa/out/_next/static/chunks/8614.caec96cfb91f4ae7.js +0 -1
  761. package/apps/pwa/out/_next/static/chunks/8637.47f5b35afcae5e6b.js +0 -1
  762. package/apps/pwa/out/_next/static/chunks/8714.ff0b7ce3ec3e3ae2.js +0 -1
  763. package/apps/pwa/out/_next/static/chunks/8732.367f44a916202353.js +0 -1
  764. package/apps/pwa/out/_next/static/chunks/9141-7fd1f73a5400bdc2.js +0 -1
  765. package/apps/pwa/out/_next/static/chunks/921.c4267d0d69a65404.js +0 -1
  766. package/apps/pwa/out/_next/static/chunks/9261.3962c3321b047691.js +0 -115
  767. package/apps/pwa/out/_next/static/chunks/936-26efd1a0efdd44d3.js +0 -1
  768. package/apps/pwa/out/_next/static/chunks/9627-4b9efe616b27e2e2.js +0 -1
  769. package/apps/pwa/out/_next/static/chunks/967-2aa39b2d04b4c690.js +0 -1
  770. package/apps/pwa/out/_next/static/chunks/9734.40eb1e558c29ccea.js +0 -1
  771. package/apps/pwa/out/_next/static/chunks/9908.3aa54f7a586a73dc.js +0 -1
  772. package/apps/pwa/out/_next/static/chunks/9954-ca8d29752bdbb45a.js +0 -1
  773. package/apps/pwa/out/_next/static/chunks/9974-507fa67cb94066dd.js +0 -1
  774. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-ce9e00f7fb45506a.js +0 -1
  775. package/apps/pwa/out/_next/static/chunks/app/approvals/page-fa59d6d17daa3389.js +0 -1
  776. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-53f68bbe7bd4b774.js +0 -1
  777. package/apps/pwa/out/_next/static/chunks/app/board/page-1c500fbb9f4a7a04.js +0 -1
  778. package/apps/pwa/out/_next/static/chunks/app/botlab/page-99d72673aa5c307c.js +0 -1
  779. package/apps/pwa/out/_next/static/chunks/app/bots/page-d2d5adc1e9484c88.js +0 -1
  780. package/apps/pwa/out/_next/static/chunks/app/ceo/page-c607c5aae637f1cc.js +0 -1
  781. package/apps/pwa/out/_next/static/chunks/app/chat/page-fa8c23596d555c0f.js +0 -1
  782. package/apps/pwa/out/_next/static/chunks/app/consult/page-bdab8c5e855c8416.js +0 -1
  783. package/apps/pwa/out/_next/static/chunks/app/control/page-406b47745b53f269.js +0 -1
  784. package/apps/pwa/out/_next/static/chunks/app/decisions/page-a4276386b7051f50.js +0 -1
  785. package/apps/pwa/out/_next/static/chunks/app/design-check/page-8a26bab9ad375f25.js +0 -1
  786. package/apps/pwa/out/_next/static/chunks/app/editor/page-775f48aa1dc7a2d5.js +0 -1
  787. package/apps/pwa/out/_next/static/chunks/app/exec/page-1c4dd5f959d0c558.js +0 -1
  788. package/apps/pwa/out/_next/static/chunks/app/field/page-c48938a8377c9f70.js +0 -1
  789. package/apps/pwa/out/_next/static/chunks/app/healthz/route-ce9e00f7fb45506a.js +0 -1
  790. package/apps/pwa/out/_next/static/chunks/app/inside/page-c6e7905859425aa9.js +0 -1
  791. package/apps/pwa/out/_next/static/chunks/app/intake/page-c280f4252452e830.js +0 -1
  792. package/apps/pwa/out/_next/static/chunks/app/layout-cf8c57498f20970a.js +0 -1
  793. package/apps/pwa/out/_next/static/chunks/app/live/page-ca3a17caf409fc83.js +0 -1
  794. package/apps/pwa/out/_next/static/chunks/app/live-run/page-26e6c18a1a750cf6.js +0 -1
  795. package/apps/pwa/out/_next/static/chunks/app/loops/page-3abc872cc6aeee39.js +0 -1
  796. package/apps/pwa/out/_next/static/chunks/app/market/page-f7481b7b2174fe3d.js +0 -1
  797. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1ae7ef8077680337.js +0 -1
  798. package/apps/pwa/out/_next/static/chunks/app/not-found-415f1e243c7ccb13.js +0 -1
  799. package/apps/pwa/out/_next/static/chunks/app/observatory/page-73f380f17b76f95c.js +0 -1
  800. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-2991073ad66ee2d9.js +0 -1
  801. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fbad60d1e358fb2c.js +0 -1
  802. package/apps/pwa/out/_next/static/chunks/app/page-398047d6949e8ca5.js +0 -1
  803. package/apps/pwa/out/_next/static/chunks/app/reflection/page-2d33a2231540ca89.js +0 -1
  804. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-5bf1e6bf70bcb96c.js +0 -1
  805. package/apps/pwa/out/_next/static/chunks/app/sessions/page-ba59c19376a0917d.js +0 -1
  806. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-111f187139add30f.js +0 -1
  807. package/apps/pwa/out/_next/static/chunks/app/settings/page-acaae197d5be5779.js +0 -1
  808. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-0df20b0f546a0d7d.js +0 -1
  809. package/apps/pwa/out/_next/static/chunks/app/setup/layout-ce9e00f7fb45506a.js +0 -1
  810. package/apps/pwa/out/_next/static/chunks/app/setup/page-27e1202d24b8fe35.js +0 -1
  811. package/apps/pwa/out/_next/static/chunks/app/share/page-bda78c50a78363e5.js +0 -1
  812. package/apps/pwa/out/_next/static/chunks/app/showroom/page-da592691293d9c9f.js +0 -1
  813. package/apps/pwa/out/_next/static/chunks/app/tasks/page-f832994317a2e9f0.js +0 -1
  814. package/apps/pwa/out/_next/static/chunks/app/today/page-cfccbbeae0473404.js +0 -1
  815. package/apps/pwa/out/_next/static/chunks/app/trace/page-527e2073af84c81b.js +0 -1
  816. package/apps/pwa/out/_next/static/chunks/app/vault/page-bac86e9112671357.js +0 -1
  817. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6d59dff410e9d76a.js +0 -1
  818. package/apps/pwa/out/_next/static/chunks/app/workflows/page-53a61f8be09f02d2.js +0 -1
  819. package/apps/pwa/out/_next/static/chunks/app/workspace/page-db8f559e763c17f9.js +0 -1
  820. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c02aa4cc66455093.js +0 -1
  821. package/apps/pwa/out/_next/static/chunks/framework-50e6acebc630849b.js +0 -1
  822. package/apps/pwa/out/_next/static/chunks/main-7a1ae342d5ed75e6.js +0 -1
  823. package/apps/pwa/out/_next/static/chunks/main-app-7ad7d505ba1f92b4.js +0 -1
  824. package/apps/pwa/out/_next/static/chunks/pages/_app-02edba75232cb235.js +0 -1
  825. package/apps/pwa/out/_next/static/chunks/pages/_error-97d6ebae3e8eee4a.js +0 -1
  826. package/apps/pwa/out/_next/static/chunks/webpack-e986adaa429163e8.js +0 -1
  827. /package/apps/pwa/out/_next/static/{T1mBQqmkwKDLGtyisDCOd → 8kITumG9GAZ1LwT9POrAR}/_ssgManifest.js +0 -0
@@ -21,7 +21,8 @@ import { POD_CREDENTIAL_TOKEN_ENV, POD_CREDENTIAL_URL_ENV } from '../../grok/cre
21
21
  import { isVerifiedOpenGatewayModel } from '../../llm/opengateway-models.js';
22
22
  import { configuredOpenGatewayKeys } from '../../llm/provider-credentials.js';
23
23
  import { collectPodLedgers, createPodLedgerFollower } from './pod-ledger-collect.js';
24
- import { collectPodArtifacts, podNoResultDiagnostic } from './pod-artifact-return.js';
24
+ import { captureFailedPodJobs } from './pod-ledger-events.js';
25
+ import { collectPodArtifacts, podNoResultDiagnostic, POD_SALVAGE_BUNDLE_FILE_LIMIT, POD_SALVAGE_BUNDLE_TOTAL_LIMIT } from './pod-artifact-return.js';
25
26
  import { readPodRepairArtifact } from './pod-repair-commit.js';
26
27
  import { isValidAccountName, resolveCodexAccount } from '../../oauth/codex-account.js';
27
28
  import { createCodexAccessMirror } from '../../oauth/codex-access-mirror.js';
@@ -30,13 +31,19 @@ import { controlInboxEnv } from '../../harness/control-inbox.js';
30
31
  import { finishPodFragment, writePodFragment } from '../../harness/self-send-target.js';
31
32
  import { mintRunId, normalizeRunId } from '../../harness/harness-space.js';
32
33
  import { TA_LAND_ENV } from '../../task-agent/task-hand.js';
33
- import { appendRunLedgerEntry, loadRunLedger, runLedgerDir, runLedgerPath } from '../../self-implement/run-ledger.js';
34
+ import { appendRunLedgerEntry, loadRunLedger, runLedgerDir, runLedgerPath, type RunLedgerEntry } from '../../self-implement/run-ledger.js';
35
+ import { recordRunStop } from '../../self-implement/run-stop.js';
34
36
  import { runSuffixKey } from '../../self-implement/salvage-branches.js';
35
37
  import type { GoalExecutionRecord } from '../../self-implement/orchestrator.js';
36
38
  import { hostRepositoryUnavailable, runHostRegate, type HostRegateResult } from '../../self-implement/host-regate.js';
37
39
  import { resolvePodHostAcpReview, runPodHostAcpReview, type PodHostAcpReviewOutcome } from './pod-host-acp-review.js';
38
40
  import { REVIEW_DEFERRED_TO_HOST_ENV, REVIEW_DEFERRED_TO_HOST_MERGE_REASON } from '../../self-implement/review-deferred-to-host.js';
39
41
  import { classifyPodTerminal, applyPodTerminal, podTerminalPrBody, type PrTerminalRecord, type DraftResidueContext } from '../../self-dev/draft-residue.js';
42
+ import { assessSiWarmReply, decideSiWarmNextStep, type SiWarmCommand, type SiWarmReply,
43
+ type SiWarmReturnedBytes } from './pod-si-warm-protocol.js';
44
+ import { cleanAndReturnChildPod, type ChildPodReuseOperations } from './pod-child-reuse.js';
45
+ import { executeSiWarmTransport, type SiWarmFixedRunner,
46
+ type SiWarmPodIdentity } from './pod-si-warm-transport.js';
40
47
 
41
48
  export const POD_CONTROL_INBOX_DIR = '/tmp/elanous-control.inbox';
42
49
  /** Pod Job 수명 상한(`activeDeadlineSeconds`) — 대표 2026-09-26 90분 → 180분(90분에 `DeadlineExceeded` 로 죽은 런 둘). */
@@ -59,14 +66,17 @@ export const POD_JOB_DEADLINE_SECONDS = 10_800;
59
66
  // POD-DIET (10-06 03:47~05:18 · node-b goal Pods · 89 one-minute samples · 42 Pods): per-Pod observed max median 4.6Gi ·
60
67
  // p75 6.2Gi · p90 11.7Gi — 23/42 went above the old 4Gi request, so the scheduler over-packed. 6Gi ≈ p75; limits stay.
61
68
  // Node allocatable ≈ 343Gi ≫ 25 × 6Gi. One-minute samples can miss the true peak (lower bound).
62
- import { podWorkLabels, type PodWorkIdentity, type PodPlacement, type PodPlacementKind, type PodPoolMember, type PodPoolScheduler } from './pod-pool.js';
69
+ import { POD_LAUNCH_FINISHED_EVENT, POD_LAUNCH_STARTED_EVENT, provenanceRowFromAnnotations, siTaskPodProvenance } from './pod-launch-history.js';
70
+ import { podProvenanceMetadata, type PodProvenanceMetadata } from './pod-provenance.js';
71
+ import { packageVersion } from '../../version/code-revision.js';
72
+ import { minihLocationFrom, podWorkLabels, type PodWorkIdentity, type PodPlacement, type PodPlacementKind, type PodPoolMember, type PodPoolScheduler } from './pod-pool.js';
63
73
  export { POD_CHILD_REQUESTS } from './pod-lease.js';
64
74
  import { measurePoolLease, recommendConcurrency, POD_CHILD_REQUESTS, POD_SI_ADMISSION_CAP_BYTES, POD_HOST_LEASE_ANNOTATION, POD_JOB_TTL_AFTER_FINISHED_SECONDS, LEASE_KUBECTL_MAX_BUFFER, memoryQuantityBytes } from './pod-lease.js';
65
75
  import { ACTUAL_SUBSTRATE_ENV, RUN_CONTRACT_ENV, carryRunContract, completionFloorFor } from '../../self-implement/graph-run-contract.js';
66
- import { execFile, spawnSync } from 'node:child_process';
76
+ import { execFile, execFileSync, spawnSync } from 'node:child_process';
67
77
  import { createHash } from 'node:crypto';
68
- import { existsSync, mkdirSync, readFileSync, realpathSync } from 'node:fs';
69
- import { homedir } from 'node:os';
78
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync } from 'node:fs';
79
+ import { homedir, tmpdir } from 'node:os';
70
80
  import { isAbsolute, join, normalize, relative, resolve, sep } from 'node:path';
71
81
  import { findGitDir } from '../../git-fs/locate.js';
72
82
  import { hostLeaseBaseDir } from '../../pod-lease/host-lease.js';
@@ -97,7 +107,7 @@ import { ZAI_POD_EGRESS_LABEL, checkZaiPodSecret, countActiveZaiJobs, decideZaiP
97
107
  import { ZAI_CODING_DEFAULT_MODEL, ZAI_OPENROUTER_FALLBACK_MODEL, zaiSettingsFromConfig, type ZaiCodingPlanSettings } from '../../llm/zai-coding-plan.js';
98
108
  import { normalizePlanningFallbackReason, selectEnhanceLlm } from '../../prompt-enhance/enhance.js';
99
109
  import { inferProviderFromModel } from '../../llm.js';
100
- import { classifyPodExit, extractPodFailureReason, podTerminalRow, type PodExitClass } from './pod-failure-reason.js';
110
+ import { classifyPodExit, extractPodFailureReason, podChildFailureAttribution, podTerminalRow, POD_TERMINATION_JOB_JSONPATH, POD_TERMINATION_PODS_JSONPATH, readPodTermination, type PodExitClass, type PodTermination } from './pod-failure-reason.js';
101
111
  import { kubectlTransientReason, retryKubectlTransient } from './kubectl-transient-retry.js';
102
112
  import { OLD_DOOR_STAMP_ENV } from '../../self-dev/old-door.js';
103
113
  import { resolveDefaultSourceHead, seedMirrorOnce, type MirrorSeedResult } from './pod-mirror-seed.js';
@@ -105,7 +115,22 @@ import { decideWarmReuse, type WarmReuseEvidenceByConsumer, type WarmReuseSwitch
105
115
  import type { WarmWorkerCommand } from './pod-warm-worker-protocol.js';
106
116
  import type { WarmPodTransportResult } from './pod-warm-worker-pod.js';
107
117
 
108
- export type Kubectl = (args: readonly string[], input?: string) => { status: number | null; stdout: string; stderr: string };
118
+ export type Kubectl = (args: readonly string[], input?: string, timeoutMs?: number) => { status: number | null; stdout: string; stderr: string };
119
+
120
+ /** TC's si-v1 transport supplies this receipt. No lease is considered reusable from a Job exit alone. */
121
+ export interface SiWarmChildAttempt {
122
+ leaseFenced: boolean;
123
+ command: SiWarmCommand | null;
124
+ /** The consumer executes this fenced transport. A provider cannot assert its own reply verdict. */
125
+ transport: { pod: SiWarmPodIdentity; runner: SiWarmFixedRunner; now?: () => number };
126
+ payload: Uint8Array;
127
+ credentialBundle: Uint8Array;
128
+ /** Collect verified ledger/artifact bytes into the existing host stores. */
129
+ adopt: (command: SiWarmCommand, reply: SiWarmReply, bytes: SiWarmReturnedBytes) => Promise<void>;
130
+ cleanup: ChildPodReuseOperations;
131
+ /** The provider fences a worker even when a transport failure hides its command receipt. */
132
+ quarantineUnknown: (reason: string) => Promise<void>;
133
+ }
109
134
  type OpenPrByBranch = { status: 'found'; prNumber: number; prUrl: string }
110
135
  | { status: 'not-found' | 'failed'; reason: string };
111
136
 
@@ -123,6 +148,13 @@ const APP_GH_AUTH_COMMAND = `bun -e '${APP_GH_AUTH_SCRIPT}'`;
123
148
  const APP_GH_EXEC = `unset GH_TOKEN GITHUB_TOKEN; GH_CONFIG_DIR="${APP_GH_CONFIG_PATH}" exec "$@"`;
124
149
 
125
150
  export interface PodSpawnOptions {
151
+ /** si-v1 living worker consumer. Absent means the existing one-shot Job path. */
152
+ siWarmAttempt?: (input: Parameters<SelfImplementJobSpawn>[0]) => Promise<SiWarmChildAttempt | null>;
153
+ /** POD-REUSE-SI-PROVIDER-WIRE: must apply cold-path host regate, ACP, terminal and repair checks before warm adoption. */
154
+ siWarmHostPostprocess?: (result: SelfImplementJobDone, attempt: SiWarmChildAttempt,
155
+ input: Parameters<SelfImplementJobSpawn>[0]) => Promise<SelfImplementJobDone>;
156
+ /** Injected test ledger; production writes to the existing run ledger. */
157
+ siWarmLedger?: (entry: RunLedgerEntry) => void;
126
158
  /** 이미지 판(라벨 elanous.commit) — 주입(시험·감독기가 이미 잰 값). 없으면 docker 로 잰다. */
127
159
  imageCommit?: string | null;
128
160
  /** codex 계정 이름(`~/.elanous/auth.json` 의 `openai-codex:<이름>`). 기본 team. */
@@ -198,6 +230,8 @@ export interface PodSpawnOptions {
198
230
  /** LAUNCH-STALL: ms a launch may wait in one stage before `launch-stalled` (test seam; default harness.launchStallMinutes · 15 min). */
199
231
  launchStallMs?: number;
200
232
  kubectl?: Kubectl;
233
+ /** Capture SSH transport test seam; host selection and placement validation remain in the production path. */
234
+ failureCaptureRemote?: (host: string, args: readonly string[]) => ReturnType<Kubectl>;
201
235
  /** Host-side regate (injected for Pod tests). */
202
236
  hostRegate?: (input: { prNumber: number; headCommit: string; repoRoot: string; goalFile?: string; noMerge?: true }) => Promise<HostRegateResult>;
203
237
  /** REVIEW-ACP-POD — Pod merge-ready PR 을 호스트 Claude ACP 로 리뷰한 뒤 재게이트(opt-in · 옵션 → env `ELANOUS_POD_HOST_ACP_REVIEW` → config `llm.podHostAcpReview`). */
@@ -774,6 +808,48 @@ export function podGithubWatchdogScript(staleSeconds: number, intervalSeconds =
774
808
  ].join('\n');
775
809
  }
776
810
 
811
+ export function podSalvageBundleScript(): string {
812
+ return `if [ "\${rc:-0}" -ne 0 ] && grep -Eq 'draft PR 생성 실패|PR (gh|guard) 실패|same-cell reuse:' "\${ELANOUS_POD_CHILD_OUTPUT:-/tmp/si.out}"; then
813
+ mkdir -p "$HOME/outbox/pod-salvage"
814
+ found_commits=0
815
+ preservation_failed=0
816
+ bundle_bytes=0
817
+ worktree_rows=$(git worktree list --porcelain) || preservation_failed=1
818
+ while IFS= read -r wt; do
819
+ [ -n "$wt" ] || continue
820
+ git -C "$wt" rev-parse --is-inside-work-tree >/dev/null 2>&1 || { preservation_failed=1; continue; }
821
+ wt_name=$(basename -- "$wt")
822
+ case "$wt_name" in ''|.*|*/*|*'..'*) preservation_failed=1; continue ;; esac
823
+ base=$(git -C "$wt" rev-parse --verify '@{u}' 2>/dev/null || git -C "$wt" rev-parse --verify origin/HEAD 2>/dev/null || git -C "$wt" rev-list --max-parents=0 HEAD | tail -n 1)
824
+ head=$(git -C "$wt" rev-parse HEAD) || { preservation_failed=1; continue; }
825
+ [ -n "$base" ] || { preservation_failed=1; continue; }
826
+ commitCount=$(git -C "$wt" rev-list --count "$base..$head") || { preservation_failed=1; continue; }
827
+ if [ "$commitCount" -eq 0 ]; then continue; fi
828
+ found_commits=1
829
+ diffStats=$(git -C "$wt" diff --numstat "$base..$head") || { preservation_failed=1; continue; }
830
+ diffLines=$(printf '%s\\n' "$diffStats" | awk '{added += $1; removed += $2} END {print added + removed}')
831
+ bundle="$HOME/outbox/pod-salvage/$wt_name.bundle"
832
+ branch=$(git -C "$wt" symbolic-ref --quiet --short HEAD) || { preservation_failed=1; continue; }
833
+ if git -C "$wt" bundle create "$bundle" "refs/heads/$branch" && [ -s "$bundle" ]; then
834
+ size=$(( $(wc -c < "$bundle") ))
835
+ if [ "$size" -gt ${POD_SALVAGE_BUNDLE_FILE_LIMIT} ] || [ $((bundle_bytes + size)) -gt ${POD_SALVAGE_BUNDLE_TOTAL_LIMIT} ]; then
836
+ preservation_failed=1
837
+ rm -f -- "$bundle"
838
+ printf 'ELANOUS_POD_SALVAGE_FAILED size-limit %s %s %s\\n' "$wt_name" "$size" "$head"
839
+ else
840
+ bundle_bytes=$((bundle_bytes + size))
841
+ printf 'ELANOUS_POD_SALVAGE_BUNDLE pod-salvage/%s.bundle %s %s %s\\n' "$wt_name" "$commitCount" "$diffLines" "$head"
842
+ fi
843
+ else
844
+ preservation_failed=1
845
+ printf 'ELANOUS_POD_SALVAGE_FAILED bundle-create %s\\n' "$wt_name"
846
+ fi
847
+ done < <(printf '%s\\n' "$worktree_rows" | awk '/^worktree / {sub(/^worktree /, ""); print}')
848
+ if [ "$preservation_failed" -ne 0 ]; then printf 'ELANOUS_POD_SALVAGE_FAILED incomplete\\n'
849
+ elif [ "$found_commits" -eq 0 ]; then printf 'ELANOUS_POD_SALVAGE_NONE no-commits\\n'; fi
850
+ fi`;
851
+ }
852
+
777
853
  export function podSalvageScript(): string {
778
854
  return [
779
855
  'if [ "${rc:-0}" -ne 0 ]; then',
@@ -1116,7 +1192,7 @@ export function podSigtermTrapScript(seqFile: string = POD_USAGE_PARTIAL_SEQ_FIL
1116
1192
  return `trap '{ [ -n "$si_pid" ] && kill "$si_pid" 2>/dev/null || true; [ -n "\${usage_partial_pid:-}" ] && kill "$usage_partial_pid" 2>/dev/null || true; si_seq=$(cat ${seqFile} 2>/dev/null || true); case "$si_seq" in ""|*[!0-9]*) si_seq=0;; esac; si_seq=$((10#$si_seq + 1)); { [ -f scripts/usage-rollup.ts ] && bun scripts/usage-rollup.ts --since 12h --partial --seq "$si_seq"; } & si_rollup_pid=$!; for si_i in 1 2 3 4 5 6 7 8; do kill -0 "$si_rollup_pid" 2>/dev/null || break; sleep 1; done; kill "$si_rollup_pid" 2>/dev/null || true; wait "$si_rollup_pid" 2>/dev/null || true; exit 143; }' TERM`;
1117
1193
  }
1118
1194
 
1119
- export function podJobManifest(o: { name: string; namespace: string; image: string; workIdentity?: PodWorkIdentity; boundPrNumber?: number; /** 레지스트리 이미지면 IfNotPresent(노드가 pull) · 반입 이미지면 Never. */ imagePullPolicy?: 'Never' | 'IfNotPresent'; repoUrl: string; source?: PodSource; repairCommitOnly?: { pr: number; baseHead: string }; hostMirror?: string; args: readonly string[]; passEnv: readonly string[]; deadlineSeconds: number; runId?: string; parentRunId?: string; armEnv?: Readonly<Record<string, string>>; hostId?: string; imageCommit?: string | null; skillEnvs?: readonly string[]; memoryLimit?: string; memoryRequest?: string; goalDoc?: string; /** AUTHOR-POD2 — run `harness say` on /creds/feature inside the Pod (authoring happens off the host). */ authorSentence?: boolean; authorGrade?: 'full' | 'lite'; grokCredential?: 'subscription' | 'api_key'; codexAccounts?: readonly string[]; appCredential?: boolean; /** Test seam — default POD_GH_STALE_SECONDS. */ githubStaleSeconds?: number; /** Which goal execution and attempt this Job is — a resumed host verifies it before following the Job (POD9). */ execution?: { key: string; attempt: number }; hostLeaseAdmitted?: boolean; /** POD-ROLE-LLM — OpenRouter 자식 Pod 에 리뷰어용 codex 자격을 깐다. */ reviewerCodexAuth?: boolean; /** BEDROCK-PROVIDER ② — bedrock 자식만: 이름 있는 AWS Secret 의 키(secretKeyRef) ⊕ 리전 ⊕ 이그레스 라벨. */ bedrock?: BedrockPodSpec; /** ZAI-POD-CHILD — glm(ZAI) 자식만: 이름 있는 ZAI Secret 의 키(secretKeyRef) ⊕ 이그레스·동시 상한 라벨(Pod ⊕ Job). */ zai?: ZaiPodSpec; /** Codex Job may use the same named Secret only for an explicitly admitted ZAI fallback. */ zaiFallback?: ZaiPodSpec; fallbackChain?: { steps: readonly FallbackStep[]; source: 'host' | 'default' }; /** TA-POD-WARMBOOT-PARITY — the child's final line (podResultMarkerShellLine), just before `exit $rc`. */ resultMarkerLine?: string }): Record<string, unknown> {
1195
+ export function podJobManifest(o: { name: string; namespace: string; image: string; workIdentity?: PodWorkIdentity; boundPrNumber?: number; /** 레지스트리 이미지면 IfNotPresent(노드가 pull) · 반입 이미지면 Never. */ imagePullPolicy?: 'Never' | 'IfNotPresent'; repoUrl: string; source?: PodSource; repairCommitOnly?: { pr: number; baseHead: string }; hostMirror?: string; args: readonly string[]; passEnv: readonly string[]; deadlineSeconds: number; runId?: string; parentRunId?: string; armEnv?: Readonly<Record<string, string>>; hostId?: string; imageCommit?: string | null; skillEnvs?: readonly string[]; memoryLimit?: string; memoryRequest?: string; goalDoc?: string; /** AUTHOR-POD2 — run `harness say` on /creds/feature inside the Pod (authoring happens off the host). */ authorSentence?: boolean; authorGrade?: 'full' | 'lite'; grokCredential?: 'subscription' | 'api_key'; codexAccounts?: readonly string[]; appCredential?: boolean; /** Test seam — default POD_GH_STALE_SECONDS. */ githubStaleSeconds?: number; /** Which goal execution and attempt this Job is — a resumed host verifies it before following the Job (POD9). */ execution?: { key: string; attempt: number }; hostLeaseAdmitted?: boolean; /** POD-ROLE-LLM — OpenRouter 자식 Pod 에 리뷰어용 codex 자격을 깐다. */ reviewerCodexAuth?: boolean; /** BEDROCK-PROVIDER ② — bedrock 자식만: 이름 있는 AWS Secret 의 키(secretKeyRef) ⊕ 리전 ⊕ 이그레스 라벨. */ bedrock?: BedrockPodSpec; /** ZAI-POD-CHILD — glm(ZAI) 자식만: 이름 있는 ZAI Secret 의 키(secretKeyRef) ⊕ 이그레스·동시 상한 라벨(Pod ⊕ Job). */ zai?: ZaiPodSpec; /** Codex Job may use the same named Secret only for an explicitly admitted ZAI fallback. */ zaiFallback?: ZaiPodSpec; fallbackChain?: { steps: readonly FallbackStep[]; source: 'host' | 'default' }; /** TA-POD-WARMBOOT-PARITY — the child's final line (podResultMarkerShellLine), just before `exit $rc`. */ resultMarkerLine?: string; /** POD-LAUNCH-PROVENANCE — the shared provenance set (labels ⊕ annotations ⊕ child env) for this si-task Job. */ provenance?: PodProvenanceMetadata }): Record<string, unknown> {
1120
1196
  if (o.repairCommitOnly && (o.source?.kind !== 'pr' || o.source.number !== o.repairCommitOnly.pr || !/^[0-9a-f]{40}$/.test(o.repairCommitOnly.baseHead) || o.args.includes('--open-pr') || o.args.includes('--merge-by-host') || o.args.includes('--auto-merge'))) {
1121
1197
  throw new Error('repair commit-only requires matching PR source and no PR/merge flags');
1122
1198
  }
@@ -1183,6 +1259,9 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
1183
1259
  // Record that fact before the main command, without treating clone completion as install completion.
1184
1260
  `printf 'ELANOUS_POD_DEPS skipped %s image-prepared-no-install\\n' "$(date +%s)" || true`,
1185
1261
  ...(o.repairCommitOnly ? [`[ "$(git rev-parse HEAD)" = '${o.repairCommitOnly.baseHead}' ] || { echo 'ELANOUS_POD_REPAIR_FAILED source-head-mismatch'; exit 8; }`] : []),
1262
+ // PR checkout and, when requested, the repair HEAD pin have passed before this child starts.
1263
+ // A supervisor input may continue the repair, but cannot launch its source again.
1264
+ ...(o.source?.kind === 'pr' ? ['export ELANOUS_POD_VERIFIED_PR_SOURCE=1'] : []),
1186
1265
  ...(goalPath ? [
1187
1266
  `mkdir -p -- "$(dirname -- ${goalPath})" && cp -- /creds/goal-doc ${goalPath} || exit 6`,
1188
1267
  ] : []),
@@ -1201,13 +1280,16 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
1201
1280
  // AUTHOR-LITE2-POD — 등급은 환경변수 `ELANOUS_AUTHOR_GRADE` 로 싣는다(옛 Pod elanous 는 모르는 환경변수를 무시하고 full 로 돈다).
1202
1281
  siTaskPodStageShell('run', 'start'),
1203
1282
  o.repairCommitOnly
1204
- ? `export ${OLD_DOOR_STAMP_ENV}=self-implement; elanous self implement "$(cat /creds/feature)" --json --no-open-pr ${quoted} > /tmp/si.out 2>&1 & si_pid=$!; wait "$si_pid"; rc=$?; ${RUN_END}; if [ "$rc" -eq 0 ]; then elanous self repair-commit-export '${o.repairCommitOnly.pr}' '${o.repairCommitOnly.baseHead}' /tmp/si.out "$PWD" "$HOME/outbox" || rc=8; fi`
1283
+ ? `export ${OLD_DOOR_STAMP_ENV}=self-implement; elanous self implement "$(cat /creds/feature)" --json --no-open-pr ${quoted} > /tmp/si.out 2>&1 & si_pid=$!; wait "$si_pid"; rc=$?; ${RUN_END}; if elanous self repair-commit-export '${o.repairCommitOnly.pr}' '${o.repairCommitOnly.baseHead}' /tmp/si.out "$PWD" "$HOME/outbox"; then rc=0; elif [ "$rc" -eq 0 ]; then rc=8; fi`
1205
1284
  : o.authorSentence
1206
1285
  ? `echo "ELANOUS_AUTHOR_ON_POD started host=$(hostname) at=$(date -u +%Y-%m-%dT%H:%M:%SZ)"; author_t0=$(date +%s); ${o.authorGrade === 'lite' || o.authorGrade === 'full' ? `ELANOUS_AUTHOR_GRADE=${o.authorGrade} ` : ''}elanous harness say --substrate local --json${quotedAsk}${quotedChildLlm} -- "$(cat /creds/feature)" > /tmp/si.out 2>&1 & si_pid=$!; wait "$si_pid"; rc=$?; ${RUN_END}; echo "ELANOUS_AUTHOR_ON_POD finished host=$(hostname) rc=$rc seconds=$(( $(date +%s) - author_t0 ))"; ${POD_RESULT_FLATTEN}`
1207
1286
  : goalPath
1208
1287
  ? `elanous harness ask ${goalPath} --json${quotedAsk}${quotedChildLlm} > /tmp/si.out 2>&1 & si_pid=$!; wait "$si_pid"; rc=$?; ${RUN_END}; ${POD_RESULT_FLATTEN}`
1209
1288
  : `export ${OLD_DOOR_STAMP_ENV}=self-implement; elanous self implement "$(cat /creds/feature)" --json ${quoted} > /tmp/si.out 2>&1 & si_pid=$!; wait "$si_pid"; rc=$?; ${RUN_END}; ${POD_RESULT_FLATTEN}`,
1210
1289
  'cat /tmp/si.out',
1290
+ ...(o.appCredential && !o.repairCommitOnly ? ['kill "$gh_watch_pid" 2>/dev/null || true'] : []),
1291
+ ...(o.repairCommitOnly ? [] : [podSalvageScript()]),
1292
+ podSalvageBundleScript(),
1211
1293
  '[ -f scripts/usage-rollup.ts ] && bun scripts/usage-rollup.ts --since 12h || echo "ELANOUS_USAGE_ROLLUP {\"measured\":false,\"reason\":\"no rollup script\"}"',
1212
1294
  // A long run's log export can pass the 5MB artifact limit; skipping it whole left no window into why a
1213
1295
  // 2h+ child failed (🅢 09-27). Keep the newest lines under the limit instead, and say so.
@@ -1224,23 +1306,27 @@ else
1224
1306
  fi`,
1225
1307
  `set -o pipefail
1226
1308
  artifact_bytes=0
1309
+ salvage_bundle_bytes=0
1227
1310
  if [ -d "$HOME/outbox" ]; then
1228
1311
  while IFS= read -r -d '' file; do
1229
1312
  [ -f "$file" ] && [ ! -L "$file" ] || continue
1230
1313
  relative=\${file#"$HOME/outbox/"}
1231
1314
  size=$(( $(wc -c < "$file") ))
1232
- if [ "$size" -gt 5242880 ] || [ $((artifact_bytes + size)) -gt 20971520 ]; then
1315
+ salvage_bundle=0
1316
+ case "$relative" in pod-salvage/*.bundle) salvage_bundle=1 ;; esac
1317
+ if { [ "$salvage_bundle" -eq 0 ] && { [ "$size" -gt 5242880 ] || [ $((artifact_bytes + size)) -gt 20971520 ]; }; } || { [ "$salvage_bundle" -eq 1 ] && { [ "$size" -gt ${POD_SALVAGE_BUNDLE_FILE_LIMIT} ] || [ $((salvage_bundle_bytes + size)) -gt ${POD_SALVAGE_BUNDLE_TOTAL_LIMIT} ]; }; }; then
1233
1318
  printf 'ELANOUS_POD_ARTIFACT_SKIPPED %s %s\\n' "$relative" "$size"
1234
1319
  continue
1235
1320
  fi
1236
1321
  path_token=$(printf '%s' "$relative" | base64 | tr -d '\\n' | tr '+/' '-_' | tr -d '=')
1237
1322
  if encoded=$(gzip -c "$file" | base64 | tr -d '\\n'); then
1238
- artifact_bytes=$((artifact_bytes + size))
1323
+ if [ "$salvage_bundle" -eq 1 ]; then salvage_bundle_bytes=$((salvage_bundle_bytes + size)); else artifact_bytes=$((artifact_bytes + size)); fi
1239
1324
  total=$(( (\${#encoded} + 7999) / 8000 ))
1240
- for ((n=1; n<=total; n++)); do
1241
- chunk=\${encoded:$(( (n-1)*8000 )):8000}
1325
+ n=1
1326
+ while IFS= read -r chunk || [ -n "$chunk" ]; do
1242
1327
  printf 'ELANOUS_POD_ARTIFACT %s %s/%s %s\\n' "$path_token" "$n" "$total" "$chunk"
1243
- done
1328
+ n=$((n + 1))
1329
+ done < <(printf '%s' "$encoded" | fold -w 8000)
1244
1330
  else
1245
1331
  printf 'ELANOUS_POD_ARTIFACT_SKIPPED %s %s\\n' "$relative" "$size"
1246
1332
  fi
@@ -1263,12 +1349,10 @@ for ledger in "\${ELANOUS_STATE_DIR:-$HOME/.elanous}"/run-ledger/*.jsonl; do
1263
1349
  done
1264
1350
  if [ "$found" -eq 0 ]; then echo ELANOUS_RUN_LEDGER_NONE; fi`,
1265
1351
  'kill "$mem_sampler_pid" 2>/dev/null || true',
1266
- ...(o.appCredential && !o.repairCommitOnly ? ['kill "$gh_watch_pid" 2>/dev/null || true'] : []),
1267
1352
  '[ -n "${usage_partial_pid:-}" ] && kill "$usage_partial_pid" 2>/dev/null || true',
1268
1353
  'wait "$mem_sampler_pid" 2>/dev/null || true',
1269
1354
  '[ -n "${usage_partial_pid:-}" ] && wait "$usage_partial_pid" 2>/dev/null || true',
1270
1355
  'tail -n 1 /tmp/si.out',
1271
- ...(o.repairCommitOnly ? [] : [podSalvageScript()]),
1272
1356
  // TA-POD-WARMBOOT-PARITY: the last line says «the child is done» so the host need not wait for the Job condition.
1273
1357
  ...(o.resultMarkerLine ? [o.resultMarkerLine] : []),
1274
1358
  'exit $rc',
@@ -1276,15 +1360,15 @@ if [ "$found" -eq 0 ]; then echo ELANOUS_RUN_LEDGER_NONE; fi`,
1276
1360
  return {
1277
1361
  apiVersion: 'batch/v1',
1278
1362
  kind: 'Job',
1279
- metadata: { name: o.name, namespace: o.namespace, labels: { 'elanous.substrate': 'pod', 'elanous.job': o.name, ...podRunLabels(o), ...(o.workIdentity ? podWorkLabels(o.workIdentity) : {}), ...(o.zai || o.zaiFallback ? { [ZAI_POD_EGRESS_LABEL]: 'true' } : {}) },
1280
- ...((o.execution || o.hostLeaseAdmitted) ? { annotations: { ...(o.execution ? { [POD_EXECUTION_KEY_ANNOTATION]: o.execution.key, [POD_ATTEMPT_ANNOTATION]: String(o.execution.attempt) } : {}), ...(o.hostLeaseAdmitted ? { [POD_HOST_LEASE_ANNOTATION]: 'true' } : {}) } } : {}) },
1363
+ metadata: { name: o.name, namespace: o.namespace, labels: { 'elanous.substrate': 'pod', 'elanous.job': o.name, ...podRunLabels(o), ...(o.workIdentity ? podWorkLabels(o.workIdentity) : {}), ...(o.zai || o.zaiFallback ? { [ZAI_POD_EGRESS_LABEL]: 'true' } : {}), ...o.provenance?.labels },
1364
+ ...((o.execution || o.hostLeaseAdmitted || o.provenance) ? { annotations: { ...o.provenance?.annotations, ...(o.execution ? { [POD_EXECUTION_KEY_ANNOTATION]: o.execution.key, [POD_ATTEMPT_ANNOTATION]: String(o.execution.attempt) } : {}), ...(o.hostLeaseAdmitted ? { [POD_HOST_LEASE_ANNOTATION]: 'true' } : {}) } } : {}) },
1281
1365
  spec: {
1282
1366
  backoffLimit: 0,
1283
1367
  ttlSecondsAfterFinished: POD_JOB_TTL_AFTER_FINISHED_SECONDS,
1284
1368
  activeDeadlineSeconds: o.deadlineSeconds,
1285
1369
  template: {
1286
1370
  // ⭐ Pod 라벨 — local 팔만 `elanous.egress/local-llm` 을 단다. docker/h1/policy-local-llm.yaml 이 그 라벨에만 호스트 LLM 포트 «하나»를 연다.
1287
- metadata: { labels: { 'elanous.job': o.name, ...podRunLabels(o), ...(o.workIdentity ? podWorkLabels(o.workIdentity) : {}), ...(o.armEnv?.ELANOUS_LLM_PROVIDER === 'local' ? { 'elanous.egress/local-llm': 'true' } : {}), ...(o.bedrock ? { [BEDROCK_POD_EGRESS_LABEL]: 'true' } : {}), ...(o.zai || o.zaiFallback ? { [ZAI_POD_EGRESS_LABEL]: 'true' } : {}) } },
1371
+ metadata: { labels: { 'elanous.job': o.name, ...podRunLabels(o), ...(o.workIdentity ? podWorkLabels(o.workIdentity) : {}), ...(o.armEnv?.ELANOUS_LLM_PROVIDER === 'local' ? { 'elanous.egress/local-llm': 'true' } : {}), ...(o.bedrock ? { [BEDROCK_POD_EGRESS_LABEL]: 'true' } : {}), ...(o.zai || o.zaiFallback ? { [ZAI_POD_EGRESS_LABEL]: 'true' } : {}), ...o.provenance?.labels }, ...(o.provenance ? { annotations: o.provenance.annotations } : {}) },
1288
1372
  spec: {
1289
1373
  restartPolicy: 'Never',
1290
1374
  securityContext: { runAsUser: 1000, fsGroup: 1000 },
@@ -1311,6 +1395,8 @@ if [ "$found" -eq 0 ]; then echo ELANOUS_RUN_LEDGER_NONE; fi`,
1311
1395
  { name: 'ELANOUS_POD_NAMESPACE', valueFrom: { fieldRef: { fieldPath: 'metadata.namespace' } } },
1312
1396
  ...(o.hostId ? [{ name: 'ELANOUS_HOST_ID', value: o.hostId }] : []),
1313
1397
  ...(o.imageCommit ? [{ name: 'ELANOUS_IMAGE_COMMIT', value: o.imageCommit }] : []),
1398
+ // POD-LAUNCH-PROVENANCE — the child knows who launched it, why and when (ELANOUS_POD_<key> · no secrets).
1399
+ ...(o.provenance?.env ?? []),
1314
1400
  // ⭐ 런 계약(graph-run-contract.ts) — 이 칸이 «실제로» Pod 이고, 부모가 정한 계약도 Pod 다. 안의 그래프가 노드마다 대조한다.
1315
1401
  { name: ACTUAL_SUBSTRATE_ENV, value: 'pod' }, { name: RUN_CONTRACT_ENV, value: carryRunContract({ substrate: 'pod' }) },
1316
1402
  ...Object.entries(o.armEnv ?? {}).map(([name, value]) => ({ name, value })),
@@ -1421,6 +1507,14 @@ function readJobPodImage(kubectl: Kubectl, namespace: string, job: string, jobUi
1421
1507
  return { podName: pod.metadata.name, podUid, podCreatedAt, imageID, digest, digestUnavailable: digest ? null : 'nonstandard-image-id' };
1422
1508
  }
1423
1509
 
1510
+ /** A repair Pod succeeds only with a verified result/bundle, even if its child gate exited 1. */
1511
+ export function repairPodExitCode(input: { state: string; repairCommitOnly: boolean; repairArtifactReady: boolean;
1512
+ disposition?: { stage?: string; ok?: boolean } }): 0 | 1 {
1513
+ if (input.state !== 'complete') return 1;
1514
+ if (input.repairCommitOnly) return input.repairArtifactReady ? 0 : 1;
1515
+ return input.disposition?.stage !== 'host-regate-failed' && input.disposition?.ok !== false ? 0 : 1;
1516
+ }
1517
+
1424
1518
  export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplementJobSpawn {
1425
1519
  if (options.repairCommitOnly && (options.source?.kind !== 'pr' || options.source.number !== options.repairCommitOnly.pr || !/^[0-9a-f]{40}$/.test(options.repairCommitOnly.baseHead))) {
1426
1520
  throw new Error('repair commit-only requires a matching PR source and exact head');
@@ -1436,10 +1530,290 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1436
1530
  if (hostMirror && !isAbsolute(hostMirror)) throw new Error('pod.hostMirror must be an absolute directory path');
1437
1531
  const hostSupervised = options.hostSupervised ?? true;
1438
1532
  const resultMarkerOn = options.resultMarker ?? !options.kubectl;
1533
+ const writeWarmLedger = options.siWarmLedger
1534
+ ?? ((entry: RunLedgerEntry) => appendRunLedgerEntry(entry, runLedgerDir(env.ELANOUS_STATE_DIR)));
1439
1535
  return (input) => {
1440
1536
  if (options.repairCommitOnly && (input.autoMerge || input.openPr || input.base !== `pr-${options.repairCommitOnly.pr}`)) {
1441
1537
  throw new Error('repair commit-only refuses auto merge, new PR, or a different base');
1442
1538
  }
1539
+ if (options.siWarmAttempt && !options.siWarmHostPostprocess) {
1540
+ throw new Error('si-v1 warm execution requires cold-path host postprocess parity');
1541
+ }
1542
+ if (options.siWarmAttempt) {
1543
+ const done = (async (): Promise<SelfImplementJobDone> => {
1544
+ let attempt: SiWarmChildAttempt;
1545
+ try {
1546
+ const acquired = await options.siWarmAttempt!(input);
1547
+ if (!acquired) {
1548
+ // No lease was fenced: use the existing one-shot Job path.
1549
+ const { siWarmAttempt: _unused, siWarmHostPostprocess: _postprocess, ...coldOptions } = options;
1550
+ return podSelfImplementSpawn(coldOptions)(input).done;
1551
+ }
1552
+ attempt = acquired;
1553
+ }
1554
+ catch (error) {
1555
+ // A lost exec reply may conceal an already opened PR. Never start another child here.
1556
+ const message = error instanceof Error ? error.message : String(error);
1557
+ debug.log('pod.reuse', 'attempt-unavailable', { spaceId: input.spaceId, reason: message }, { level: 'warn' });
1558
+ return { exitCode: 1, output: '', error: { code: 'si-warm-reconcile-only', message } };
1559
+ }
1560
+ let transport: Awaited<ReturnType<typeof executeSiWarmTransport>>;
1561
+ try {
1562
+ transport = await executeSiWarmTransport({ command: attempt.command,
1563
+ pod: attempt.transport.pod, runner: attempt.transport.runner,
1564
+ leaseFenced: attempt.leaseFenced, payload: attempt.payload,
1565
+ credentialBundle: attempt.credentialBundle, now: attempt.transport.now });
1566
+ } catch (error) {
1567
+ const message = error instanceof Error ? error.message : String(error);
1568
+ try { await attempt.quarantineUnknown('transport-exception'); }
1569
+ catch (quarantineError) {
1570
+ // Both failures must survive: the first transport error names the cause, the
1571
+ // quarantine error names the unfinished disposition. Record the judgement.
1572
+ const quarantineMessage = quarantineError instanceof Error ? quarantineError.message : String(quarantineError);
1573
+ const combined = `transport:${message}; quarantine:${quarantineMessage}`;
1574
+ try { writeWarmLedger({ runId: attempt.command?.runId ?? input.spaceId,
1575
+ event: 'pod-reuse-quarantine-failed',
1576
+ data: { spaceId: input.spaceId, workerId: attempt.command?.workerId ?? null,
1577
+ leaseId: attempt.command?.leaseId ?? null, reason: combined } }); }
1578
+ catch (ledgerError) { debug.log('pod.reuse', 'ledger-unavailable', { spaceId: input.spaceId,
1579
+ event: 'pod-reuse-quarantine-failed', reason: ledgerError instanceof Error ? ledgerError.message : String(ledgerError) }, { level: 'warn' }); }
1580
+ debug.log('pod.reuse', 'quarantine-failed', { spaceId: input.spaceId, reason: combined }, { level: 'error' });
1581
+ return { exitCode: 1, output: '', error: { code: 'si-warm-worker-state-unknown', message: combined } };
1582
+ }
1583
+ // The quarantine is confirmed: record the settled disposal (fail-soft — the
1584
+ // disposition survives a ledger write failure) before the reconcile-only return.
1585
+ try { writeWarmLedger({ runId: attempt.command?.runId ?? input.spaceId,
1586
+ event: 'pod-reuse-quarantined',
1587
+ data: { spaceId: input.spaceId, workerId: attempt.command?.workerId ?? null,
1588
+ leaseId: attempt.command?.leaseId ?? null, reason: 'transport-exception' } }); }
1589
+ catch (ledgerError) { debug.log('pod.reuse', 'ledger-unavailable', { spaceId: input.spaceId,
1590
+ event: 'pod-reuse-quarantined', reason: ledgerError instanceof Error ? ledgerError.message : String(ledgerError) }, { level: 'warn' }); }
1591
+ return { exitCode: 1, output: '', error: { code: 'si-warm-reconcile-only', message } };
1592
+ }
1593
+ const validation = transport.boundary === 'exec-attempted'
1594
+ ? assessSiWarmReply(attempt.command, transport.reply, transport.bytes,
1595
+ attempt.payload, attempt.credentialBundle)
1596
+ : undefined;
1597
+ const next = decideSiWarmNextStep({ boundary: transport.boundary,
1598
+ leaseFenced: attempt.leaseFenced, ...(validation ? { validation } : {}) });
1599
+ debug.log('pod.reuse', 'admission-decision', { spaceId: input.spaceId, boundary: transport.boundary,
1600
+ next: next.next, reason: transport.reason, validationReason: transport.validationReason ?? null });
1601
+ if (next.next === 'cold-job' && transport.boundary === 'not-entered' && attempt.leaseFenced) {
1602
+ const { siWarmAttempt: _unused, ...coldOptions } = options;
1603
+ // The acquired lease must not silently ride along into a cold Job: dispose it first,
1604
+ // record the disposition, and refuse to start the cold Job when disposal fails.
1605
+ const coldLease = attempt.command ? { workerId: attempt.command.workerId,
1606
+ podUid: attempt.command.podUid, leaseId: attempt.command.leaseId,
1607
+ generation: attempt.command.generation, key: attempt.command.key,
1608
+ sourceSha: attempt.command.sourceSha } : null;
1609
+ if (coldLease) {
1610
+ const recordColdDisposition = (event: string, action: string, residue: Record<string, unknown> = {}) => {
1611
+ debug.log('pod.reuse', event, { spaceId: input.spaceId, workerId: coldLease.workerId,
1612
+ leaseId: coldLease.leaseId, action, reason: transport.reason, ...residue });
1613
+ try { writeWarmLedger({ runId: attempt.command?.runId ?? input.spaceId, event,
1614
+ data: { spaceId: input.spaceId, workerId: coldLease.workerId, leaseId: coldLease.leaseId,
1615
+ action, reason: transport.reason, ...residue } }); }
1616
+ catch (error) { debug.log('pod.reuse', 'ledger-unavailable', { spaceId: input.spaceId, event,
1617
+ reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
1618
+ };
1619
+ try {
1620
+ // The acquired lease is disposed by the same eligibility standard as the
1621
+ // entered path: process count zero, pinned-source git reset/clean/status,
1622
+ // credential count zero, and an observable return decision — before the
1623
+ // lease can go back into the warm pool or a cold Job starts.
1624
+ const returned = await cleanAndReturnChildPod(coldLease, { ...attempt.cleanup,
1625
+ observe: (event, data) => {
1626
+ debug.log('pod.reuse', event, { spaceId: input.spaceId, ...data },
1627
+ { level: event === 'return-decision'
1628
+ && ((data as { outcome?: string }).outcome === 'eligible'
1629
+ || (data as { outcome?: string }).outcome === 'returned') ? 'info' : 'warn' });
1630
+ attempt.cleanup.observe(event, data);
1631
+ // A ledger write failure is an observation failure: the settled return
1632
+ // or quarantine disposition must survive it (fail-soft after the decision).
1633
+ try { writeWarmLedger({ runId: attempt.command?.runId ?? input.spaceId,
1634
+ event: `pod-reuse-${event}`,
1635
+ data: { spaceId: input.spaceId, ...data } }); }
1636
+ catch (ledgerError) { debug.log('pod.reuse', 'ledger-unavailable', { spaceId: input.spaceId,
1637
+ runId: attempt.command?.runId ?? input.spaceId, event: `pod-reuse-${event}`,
1638
+ reason: ledgerError instanceof Error ? ledgerError.message : String(ledgerError) },
1639
+ { level: 'warn' }); }
1640
+ } });
1641
+ recordColdDisposition('pod-reuse-cold-fallback-dispose', returned.outcome,
1642
+ { dispositionReason: returned.reason });
1643
+ } catch (error) {
1644
+ const message = error instanceof Error ? error.message : String(error);
1645
+ try { await attempt.cleanup.quarantine(coldLease, { clean: false, reason: 'cold-fallback-dispose-failed', observedResidue: null }); }
1646
+ catch (quarantineError) {
1647
+ const quarantineMessage = quarantineError instanceof Error ? quarantineError.message : String(quarantineError);
1648
+ recordColdDisposition('pod-reuse-cold-fallback-dispose-failed', 'quarantine-failed',
1649
+ { error: `disposal:${message}; quarantine:${quarantineMessage}` });
1650
+ return { exitCode: 1, output: '', error: { code: 'si-warm-worker-state-unknown',
1651
+ message: `disposal:${message}; quarantine:${quarantineMessage}` } };
1652
+ }
1653
+ recordColdDisposition('pod-reuse-cold-fallback-dispose-failed', 'quarantined', { error: message });
1654
+ return { exitCode: 1, output: '', error: { code: 'si-warm-worker-state-unknown', message } };
1655
+ }
1656
+ }
1657
+ return podSelfImplementSpawn(coldOptions)(input).done;
1658
+ }
1659
+ const command = attempt.command;
1660
+ const lease = command && { workerId: command.workerId, podUid: command.podUid,
1661
+ leaseId: command.leaseId, generation: command.generation, key: command.key,
1662
+ sourceSha: command.sourceSha };
1663
+ const recordQuarantine = (event: string, reason: string, residue: Record<string, unknown> = {}) => {
1664
+ debug.log('pod.reuse', event, { spaceId: input.spaceId, workerId: command?.workerId ?? null,
1665
+ leaseId: command?.leaseId ?? null, reason, ...residue }, { level: 'warn' });
1666
+ try { writeWarmLedger({ runId: command?.runId ?? input.spaceId, event,
1667
+ data: { spaceId: input.spaceId, workerId: command?.workerId ?? null,
1668
+ leaseId: command?.leaseId ?? null, reason, ...residue } }); }
1669
+ catch (error) { debug.log('pod.reuse', 'ledger-unavailable', { spaceId: input.spaceId,
1670
+ event, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
1671
+ };
1672
+ const retireLease = async (reason: string) => {
1673
+ recordQuarantine('pod-reuse-quarantine-intent', reason);
1674
+ if (!lease) {
1675
+ try { await attempt.quarantineUnknown(reason); }
1676
+ catch (error) {
1677
+ // The unsettled quarantine is an account of its own: keep both the
1678
+ // primary path reason and the quarantine cause in the run ledger,
1679
+ // then let the caller surface the unknown worker state.
1680
+ const message = error instanceof Error ? error.message : String(error);
1681
+ recordQuarantine('pod-reuse-quarantine-failed', `${reason}; quarantine:${message}`);
1682
+ throw error;
1683
+ }
1684
+ recordQuarantine('pod-reuse-quarantined', reason);
1685
+ return;
1686
+ }
1687
+ // An uncertain exec may still be running. Keep it fenced and revoke its run credentials.
1688
+ let processRemaining: number | null = null;
1689
+ let credentialRemaining: number | null = null;
1690
+ try { processRemaining = (await attempt.cleanup.stopChildProcesses(lease)).remaining; }
1691
+ catch (error) { debug.log('pod.reuse', 'process-stop-failed', { spaceId: input.spaceId,
1692
+ reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
1693
+ try { credentialRemaining = (await attempt.cleanup.removeRunCredentials(lease)).remaining; }
1694
+ catch (error) { debug.log('pod.reuse', 'credential-revoke-failed', { spaceId: input.spaceId,
1695
+ reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
1696
+ const observedResidue = [processRemaining, credentialRemaining]
1697
+ .filter((value): value is number => typeof value === 'number'
1698
+ && Number.isSafeInteger(value) && value >= 0)
1699
+ .reduce((sum, value) => sum + value, 0);
1700
+ try {
1701
+ await attempt.cleanup.quarantine(lease, { clean: false, reason,
1702
+ observedResidue: observedResidue > 0 ? observedResidue : null });
1703
+ } catch (error) {
1704
+ // The lease stays unfenced and the worker state unknown: record the
1705
+ // unsettled disposal (primary reason plus the quarantine cause)
1706
+ // before the caller reports the unknown worker state.
1707
+ const message = error instanceof Error ? error.message : String(error);
1708
+ recordQuarantine('pod-reuse-quarantine-failed', `${reason}; quarantine:${message}`);
1709
+ throw error;
1710
+ }
1711
+ recordQuarantine('pod-reuse-quarantined', reason, { processRemaining, credentialRemaining });
1712
+ };
1713
+ if (next.next !== 'adopt-warm' || !transport.measured || !command
1714
+ || !transport.reply || !transport.bytes) {
1715
+ const reconcileDisposition = transport.verifiedBytesForReconcile
1716
+ ? parseSelfImplementJson(new TextDecoder().decode(transport.verifiedBytesForReconcile.terminal))
1717
+ : null;
1718
+ const hostDisposition = reconcileDisposition ? (({ worktreePath: _podOnly, ...host }) => host)(reconcileDisposition) : null;
1719
+ let quarantineError: string | undefined;
1720
+ try { await retireLease(validation?.reason ?? 'response-unknown'); }
1721
+ catch (error) { debug.log('pod.reuse', 'quarantine-failed', { spaceId: input.spaceId,
1722
+ reason: error instanceof Error ? error.message : String(error) }, { level: 'error' });
1723
+ quarantineError = error instanceof Error ? error.message : String(error); }
1724
+ return { exitCode: 1, output: '',
1725
+ ...(hostDisposition ? { disposition: hostDisposition } : {}),
1726
+ error: { code: quarantineError
1727
+ || (transport.quarantineWorker && !transport.quarantineRecorded)
1728
+ ? 'si-warm-worker-state-unknown' : 'si-warm-reconcile-only',
1729
+ message: `si-v1 exec may have run; reconcile before retry (${transport.reason})${transport.quarantineError ? `; transport quarantine: ${transport.quarantineError}` : ''}${quarantineError ? `; worker-state-unknown: ${quarantineError}` : ''}` } };
1730
+ }
1731
+ const terminal = new TextDecoder().decode(transport.bytes.terminal);
1732
+ const disposition = parseSelfImplementJson(terminal);
1733
+ if (!disposition) {
1734
+ let quarantineError: string | undefined;
1735
+ try { await retireLease('terminal-disposition-unreadable'); }
1736
+ catch (error) { debug.log('pod.reuse', 'quarantine-failed', { spaceId: input.spaceId,
1737
+ reason: error instanceof Error ? error.message : String(error) }, { level: 'error' });
1738
+ quarantineError = error instanceof Error ? error.message : String(error); }
1739
+ return { exitCode: 1, output: '', error: { code: quarantineError
1740
+ ? 'si-warm-worker-state-unknown' : 'si-warm-terminal-unreadable',
1741
+ message: quarantineError ?? 'validated si-v1 terminal was not a self-implement disposition' } };
1742
+ }
1743
+ try { await attempt.adopt(command, transport.reply, transport.bytes); }
1744
+ catch (error) {
1745
+ const message = error instanceof Error ? error.message : String(error);
1746
+ let quarantineError: string | undefined;
1747
+ try { await retireLease('adopt-failed'); }
1748
+ catch (quarantineFailure) { debug.log('pod.reuse', 'quarantine-failed', { spaceId: input.spaceId,
1749
+ reason: quarantineFailure instanceof Error ? quarantineFailure.message : String(quarantineFailure) }, { level: 'error' });
1750
+ quarantineError = quarantineFailure instanceof Error ? quarantineFailure.message : String(quarantineFailure); }
1751
+ debug.log('pod.reuse', 'adopt-failed', { spaceId: input.spaceId, reason: message }, { level: 'warn' });
1752
+ return { exitCode: 1, output: '', disposition, error: { code: quarantineError
1753
+ ? 'si-warm-worker-state-unknown' : 'si-warm-reconcile-only', message: quarantineError ?? message } };
1754
+ }
1755
+ let returnOutcome: 'returned' | 'quarantined' = 'quarantined';
1756
+ let workerStateUnknown: string | undefined;
1757
+ try {
1758
+ const returned = await cleanAndReturnChildPod(lease!, { ...attempt.cleanup,
1759
+ observe: (event, data) => {
1760
+ debug.log('pod.reuse', event, { spaceId: input.spaceId, ...data },
1761
+ { level: event === 'return-decision' && (data.outcome === 'eligible' || data.outcome === 'returned') ? 'info' : 'warn' });
1762
+ attempt.cleanup.observe(event, data);
1763
+ // The lease disposition is already settled by the time this callback runs; the warm
1764
+ // ledger is an observation of it. A ledger write failure must stay an observation
1765
+ // failure — escaping here would quarantine a lease that was already returned.
1766
+ try { writeWarmLedger({ runId: command.runId, event: `pod-reuse-${event}`,
1767
+ data: { spaceId: input.spaceId, ...data } }); }
1768
+ catch (error) { debug.log('pod.reuse', 'ledger-unavailable', { spaceId: input.spaceId, runId: command.runId,
1769
+ event: `pod-reuse-${event}`, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
1770
+ } });
1771
+ returnOutcome = returned.outcome;
1772
+ } catch (error) {
1773
+ workerStateUnknown = error instanceof Error ? error.message : String(error);
1774
+ debug.log('pod.reuse', 'return-unavailable', { spaceId: input.spaceId,
1775
+ reason: workerStateUnknown }, { level: 'error' });
1776
+ // The inner return/quarantine result is unclear: quarantine the lease explicitly,
1777
+ // and only a second quarantine failure leaves the worker state unknown.
1778
+ try {
1779
+ await attempt.quarantineUnknown(`return-quarantine-unavailable:${workerStateUnknown}`);
1780
+ try { writeWarmLedger({ runId: command!.runId, event: 'pod-reuse-quarantined',
1781
+ data: { spaceId: input.spaceId, workerId: command!.workerId, leaseId: command!.leaseId,
1782
+ reason: `return-quarantine-unavailable:${workerStateUnknown}` } }); }
1783
+ catch { /* ledger loss must not mask the quarantine outcome */ }
1784
+ // The explicit quarantine settled the disposal: the caller result must
1785
+ // account for it as quarantined instead of claiming an unknown state.
1786
+ workerStateUnknown = undefined;
1787
+ } catch (quarantineError) {
1788
+ const quarantineMessage = quarantineError instanceof Error ? quarantineError.message : String(quarantineError);
1789
+ workerStateUnknown = `return:${workerStateUnknown}; quarantine:${quarantineMessage}`;
1790
+ try { writeWarmLedger({ runId: command!.runId, event: 'pod-reuse-quarantine-failed',
1791
+ data: { spaceId: input.spaceId, workerId: command!.workerId, leaseId: command!.leaseId,
1792
+ reason: workerStateUnknown } }); }
1793
+ catch { debug.log('pod.reuse', 'ledger-unavailable', { spaceId: input.spaceId,
1794
+ event: 'pod-reuse-quarantine-failed', reason: workerStateUnknown }, { level: 'warn' }); }
1795
+ }
1796
+ }
1797
+ const log = new TextDecoder().decode(transport.bytes.log);
1798
+ // Pod checkout paths cannot be used by the host supervisor.
1799
+ const { worktreePath: _podOnly, ...hostDisposition } = disposition;
1800
+ if (workerStateUnknown) return { exitCode: 1, output: log.slice(-4096), disposition: hostDisposition,
1801
+ error: { code: 'si-warm-worker-state-unknown', message: workerStateUnknown } };
1802
+ debug.log('pod.reuse', 'child-result', { spaceId: input.spaceId, workerId: command.workerId,
1803
+ leaseId: command.leaseId, returnOutcome, stage: disposition.stage ?? null });
1804
+ try {
1805
+ return await options.siWarmHostPostprocess!({ exitCode: transport.reply.exitCode,
1806
+ output: log.slice(-4096), disposition: hostDisposition }, attempt, input);
1807
+ } catch (error) {
1808
+ const message = error instanceof Error ? error.message : String(error);
1809
+ debug.log('pod.reuse', 'host-postprocess-failed', { spaceId: input.spaceId,
1810
+ reason: message }, { level: 'error' });
1811
+ return { exitCode: 1, output: log.slice(-4096), disposition: hostDisposition,
1812
+ error: { code: 'si-warm-host-postprocess-failed', message } };
1813
+ }
1814
+ })();
1815
+ return { address: `self-impl:${input.spaceId}`, done };
1816
+ }
1443
1817
  const name = podJobName(input.spaceId);
1444
1818
  let parentGoal: string | undefined;
1445
1819
  let parentGoalUnreadable = false;
@@ -1574,13 +1948,16 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1574
1948
  const acquireSlot = async (): Promise<PodPoolMember | null> => {
1575
1949
  if (!options.pool) return null;
1576
1950
  const stop = watchStall('pool-slot');
1951
+ // MINIH-MODE (0.2.37): the location is declared by the launch env — absent/unknown keeps the configured
1952
+ // capacities, so this launch path behaves exactly as before unless the declaration is there.
1953
+ const minihLocation = minihLocationFrom(env.ELANOUS_MINIH_LOCATION);
1577
1954
  try {
1578
1955
  for (;;) {
1579
1956
  if (input.signal?.aborted) return null;
1580
1957
  // The reason comes back inside this call, so a concurrent launch cannot overwrite it.
1581
1958
  const kind = podPlacementKind(oomRetried ? retryTier : memoryTier, selectedGoalType, requestKind);
1582
1959
  const acquired = await options.pool.tryAcquire(unfitContexts, (reason) => { stageReason['pool-slot'] = reason; },
1583
- { kind, onPlacement: (placement) => { lastPlacement = placement; } });
1960
+ { kind, ...(minihLocation ? { minihLocation } : {}), onPlacement: (placement) => { lastPlacement = placement; } });
1584
1961
  if (acquired) {
1585
1962
  debug.log('self-implement.pod', 'pool-slot', { spaceId: input.spaceId, context: acquired.context, inflight: options.pool.snapshot(), ...(lastPlacement ? { placement: lastPlacement } : {}) });
1586
1963
  if (lastPlacement) console.warn(`[pod] 배치: ${lastPlacement.reason}`);
@@ -1631,6 +2008,21 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1631
2008
  const currentContext = member ? null : baseKubectl(['config', 'current-context']);
1632
2009
  let context = member?.context ?? (currentContext?.status === 0 ? currentContext.stdout.trim() : '');
1633
2010
  const kubectl: Kubectl = (args, stdin) => baseKubectl(['--context', context, ...args], stdin);
2011
+ const captureKubectl = (host: string, args: readonly string[]): ReturnType<Kubectl> => {
2012
+ if (member?.sshHost ? member.sshHost !== host : host !== resolveHostId(env))
2013
+ return { status: 1, stdout: '', stderr: 'ledger host is not the selected pool host' };
2014
+ if (!member?.sshHost) return baseKubectl(args);
2015
+ if (options.failureCaptureRemote) return options.failureCaptureRemote(host, args);
2016
+ const result = spawnSync('ssh', ['-o', 'BatchMode=yes', '-o', 'ConnectTimeout=5', '--', host, 'kubectl', ...args],
2017
+ { encoding: 'utf8', timeout: 30_000, maxBuffer: LEASE_KUBECTL_MAX_BUFFER });
2018
+ return { status: result.status, stdout: result.stdout ?? '', stderr: (result.stderr ?? '') + (result.error ? String(result.error) : '') };
2019
+ };
2020
+ const captureFailedJob = (childRunId: string): void => {
2021
+ try {
2022
+ captureFailedPodJobs({ runId: ledgerRunId, job: name, childRunId, ledgerPath: runLedgerPath(ledgerRunId, ledgerDir), artifactDir: join(effectiveInstanceRoot(), 'pod-artifacts'),
2023
+ kubectl: captureKubectl, record: (event, data) => appendRunLedgerEntry({ runId: ledgerRunId, event, data }, ledgerDir), now: options.now });
2024
+ } catch (error) { debug.log('self-implement.pod', 'failure-capture-ledger-unavailable', { job: name, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
2025
+ };
1634
2026
  let recorded = false;
1635
2027
  let groundingMinted: string | null = null;
1636
2028
  try {
@@ -1672,6 +2064,44 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1672
2064
  collectPodArtifacts(full.stdout, { dir: join(effectiveInstanceRoot(), 'pod-artifacts'), job: name, log: record });
1673
2065
  }
1674
2066
  catch (error) { record('self-implement.pod', 'artifact-collect-incomplete', { job: name, reason: error instanceof Error ? error.message : String(error) }); }
2067
+ for (const line of full.stdout.split(/\r?\n/)) {
2068
+ const bundle = /^ELANOUS_POD_SALVAGE_BUNDLE (pod-salvage\/[a-zA-Z0-9._-]+\.bundle) ([0-9]+) ([0-9]+)(?: ([0-9a-f]{40,64}))?$/.exec(line);
2069
+ if (bundle) {
2070
+ const bundlePath = join(effectiveInstanceRoot(), 'pod-artifacts', name, bundle[1]!);
2071
+ if (!existsSync(bundlePath)) {
2072
+ try { appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-salvage-bundle', data: { job: name, disposition: '보존 실패', reason: 'bundle artifact missing' } }, ledgerDir); }
2073
+ catch (error) { record('self-implement.pod', 'salvage-bundle-ledger-incomplete', { job: name, reason: error instanceof Error ? error.message : String(error) }); }
2074
+ record('self-implement.pod', 'salvage-bundle-collect-incomplete', { job: name, path: bundlePath, reason: 'bundle artifact missing' });
2075
+ continue;
2076
+ }
2077
+ try {
2078
+ const refs = execFileSync('git', ['bundle', 'list-heads', bundlePath], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim().split(/\r?\n/);
2079
+ const [bundleHead, bundleRef] = refs[0]?.trim().split(/\s+/) ?? [];
2080
+ if (!bundleHead || !bundleRef?.startsWith('refs/heads/')) throw new Error('bundle contains no branch ref');
2081
+ const verifyDir = mkdtempSync(join(tmpdir(), 'pod-salvage-verify-'));
2082
+ try {
2083
+ execFileSync('git', ['clone', '--quiet', '--branch', bundleRef.slice('refs/heads/'.length), bundlePath, verifyDir], { stdio: ['ignore', 'pipe', 'pipe'] });
2084
+ const cloneHead = execFileSync('git', ['-C', verifyDir, 'rev-parse', 'HEAD'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
2085
+ if (cloneHead !== bundleHead || (bundle[4] && cloneHead !== bundle[4])) throw new Error('bundle clone HEAD mismatch');
2086
+ } finally { rmSync(verifyDir, { recursive: true, force: true }); }
2087
+ appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-salvage-bundle', data: {
2088
+ job: name, path: bundlePath,
2089
+ commitCount: Number(bundle[2]), diffLines: Number(bundle[3]), ...(bundle[4] ? { head: bundle[4] } : {}),
2090
+ } }, ledgerDir);
2091
+ } catch (error) {
2092
+ const reason = error instanceof Error ? error.message : String(error);
2093
+ try { appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-salvage-bundle', data: { job: name, disposition: '보존 실패', reason: `bundle validation failed: ${reason}` } }, ledgerDir); }
2094
+ catch (ledgerError) { record('self-implement.pod', 'salvage-bundle-ledger-incomplete', { job: name, reason: ledgerError instanceof Error ? ledgerError.message : String(ledgerError) }); }
2095
+ record('self-implement.pod', 'salvage-bundle-collect-incomplete', { job: name, reason });
2096
+ }
2097
+ } else if (line === 'ELANOUS_POD_SALVAGE_NONE no-commits') {
2098
+ try { appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-salvage-bundle', data: { job: name, disposition: '보존할 것 없음', commitCount: 0, diffLines: 0 } }, ledgerDir); }
2099
+ catch (error) { record('self-implement.pod', 'salvage-bundle-ledger-incomplete', { job: name, reason: error instanceof Error ? error.message : String(error) }); }
2100
+ } else if (line.startsWith('ELANOUS_POD_SALVAGE_FAILED ')) {
2101
+ try { appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-salvage-bundle', data: { job: name, disposition: '보존 실패', reason: line.slice('ELANOUS_POD_SALVAGE_FAILED '.length) } }, ledgerDir); }
2102
+ catch (error) { record('self-implement.pod', 'salvage-bundle-ledger-incomplete', { job: name, reason: error instanceof Error ? error.message : String(error) }); }
2103
+ }
2104
+ }
1675
2105
  if (options.repairCommitOnly) {
1676
2106
  const artifactDir = join(effectiveInstanceRoot(), 'pod-artifacts', name, 'repair');
1677
2107
  try {
@@ -1699,9 +2129,11 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1699
2129
  // 상한 안 → ZAI(이름 있는 Secret · 없으면 거부) · 넘침 → 정책대로(openrouter·balance = 기존 OpenRouter GLM · stop = 거부 · apply 0).
1700
2130
  let zaiRoute: 'zai' | 'openrouter' | undefined;
1701
2131
  let zaiSpec: ZaiPodSpec | undefined;
2132
+ let zaiLaunchOverage: 'stop' | 'balance' | 'openrouter' | undefined;
1702
2133
  zaiPendingApply.delete(name);
1703
2134
  if (options.provider === 'glm') {
1704
2135
  const settings = (options.zaiSettings ?? (() => zaiSettingsFromConfig(getUserConfig().llm)))();
2136
+ zaiLaunchOverage = settings.overage;
1705
2137
  const poolMembers = (options.pool as { members?: readonly PodPoolMember[] } | undefined)?.members;
1706
2138
  const contexts = Array.isArray(poolMembers) && poolMembers.length > 0 ? poolMembers.map((m) => m.context) : [context];
1707
2139
  let active: number | null = 0;
@@ -1733,6 +2165,8 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1733
2165
  const anthropic = options.provider === 'anthropic';
1734
2166
  const bedrockChild = options.provider === 'bedrock';
1735
2167
  const zaiChild = zaiRoute === 'zai';
2168
+ /** ZAI 로 입장한 자식도 플랜 한도 오류 뒤 OpenRouter 로 전환할 수 있다. 실행 중 폴백 자격을 발사 전에 확보한다. */
2169
+ const openrouterCredentialRequired = openrouter || (zaiChild && zaiLaunchOverage !== 'stop');
1736
2170
  /** API 키 자식 — codex 계정·자격 파일을 안 쓴다(키는 작업별 creds Secret 의 `env-<KEY>` · bedrock·glm 은 이름 있는 Secret 으로만). */
1737
2171
  const apiKeyChild = openrouter || openGateway || anthropic || bedrockChild || zaiChild;
1738
2172
  const plannedAccount = retryAccount ?? (grok ? 'grok' : openrouter ? 'openrouter' : openGateway ? 'opengateway' : anthropic ? 'anthropic' : bedrockChild ? 'bedrock' : zaiChild ? 'glm' : options.accountBroker?.() ?? options.account ?? 'team');
@@ -1776,8 +2210,8 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1776
2210
  }
1777
2211
  return credential;
1778
2212
  }) : undefined;
1779
- const openrouterKey = openrouter ? env.OPENROUTER_API_KEY?.trim() || (options.readKeyCache ?? defaultReadKeyCache)('OPENROUTER_API_KEY')?.trim() : undefined;
1780
- if (openrouter && !openrouterKey) throw new Error('pod: Pod 에 openrouter 키 없음 — 호스트 키 캐시 또는 OPENROUTER_API_KEY 필요');
2213
+ const openrouterKey = openrouterCredentialRequired ? env.OPENROUTER_API_KEY?.trim() || (options.readKeyCache ?? defaultReadKeyCache)('OPENROUTER_API_KEY')?.trim() : undefined;
2214
+ if (openrouterCredentialRequired && !openrouterKey) throw new Error('pod: Pod 에 openrouter 키 없음 — 호스트 키 캐시 또는 OPENROUTER_API_KEY 필요');
1781
2215
  const openGatewayEnvKey = openGateway ? env.OPENGATEWAY_API_KEY?.trim() : undefined;
1782
2216
  const openGatewayCachedKey = openGateway ? (options.readKeyCache ?? defaultReadKeyCache)('OPENGATEWAY_API_KEY')?.trim() : undefined;
1783
2217
  if (openGatewayEnvKey && openGatewayCachedKey && openGatewayEnvKey !== openGatewayCachedKey)
@@ -1859,7 +2293,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1859
2293
  }
1860
2294
  }
1861
2295
  // Grok 자격은 전용 Secret 키 하나로만 보낸다 — passEnv 의 API 키 중복 전달은 막는다.
1862
- const passKeys = [...new Set([...(options.passEnv ?? []), ...(openrouter ? ['OPENROUTER_API_KEY'] : []), ...(openGateway ? ['OPENGATEWAY_API_KEY'] : []), ...(anthropic ? ['ANTHROPIC_API_KEY'] : [])])].filter((key) => key !== 'ELANOUS_LIVE_DETAIL_UNTIL'
2296
+ const passKeys = [...new Set([...(options.passEnv ?? []), ...(openrouterCredentialRequired ? ['OPENROUTER_API_KEY'] : []), ...(openGateway ? ['OPENGATEWAY_API_KEY'] : []), ...(anthropic ? ['ANTHROPIC_API_KEY'] : [])])].filter((key) => key !== 'ELANOUS_LIVE_DETAIL_UNTIL'
1863
2297
  && key !== POD_GITHUB_CREDENTIAL_TOKEN_ENV && key !== POD_GITHUB_CREDENTIAL_URL_ENV
1864
2298
  && (!grok || !['XAI_API_KEY', 'GROK_API_KEY', 'GROK_CODE_XAI_API_KEY'].includes(key)));
1865
2299
  const skillEnvs: Record<string, string> = options.skillEnv
@@ -1984,6 +2418,13 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1984
2418
  if (key.startsWith('ELANOUS_PROMPT_ENHANCE_') && key !== 'ELANOUS_PROMPT_ENHANCE_MODEL' && key !== 'ELANOUS_PROMPT_ENHANCE_EFFORT') delete hostAuthorEnv[key];
1985
2419
  }
1986
2420
  let authorChoice = authorOnPod ? selectEnhanceLlm({ config: (options.authorConfig ?? getUserConfig)() }, hostAuthorEnv) : undefined;
2421
+ // A named child model is this launch's model when planning has no explicit choice.
2422
+ // Relay it to authoring too: the Pod's persistent grounding goal loop otherwise
2423
+ // receives the host's default "better" model and silently spends sol for a luna child.
2424
+ const namedChildModel = podDelegatedChildLlm(jobArgs)?.model;
2425
+ if (authorChoice?.modelSource === 'default' && namedChildModel) {
2426
+ authorChoice = { ...authorChoice, model: namedChildModel, modelSource: 'child' };
2427
+ }
1987
2428
  let authorFallbackReason: string | undefined;
1988
2429
  let authorRequestedModel: string | undefined;
1989
2430
  let authorRequestedSource: string | undefined;
@@ -2005,8 +2446,10 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2005
2446
  authorChoice = { ...authorChoice, model: childModel, modelSource: 'fallback-child' };
2006
2447
  } else debug.log('self-implement.pod', 'role-llm-armed', { job: name, role: 'planning', model: authorChoice.model, source: authorChoice.modelSource, account: roleCodexAccount });
2007
2448
  }
2008
- const hostKey = (k: string): string | undefined => k === 'OPENROUTER_API_KEY' && openrouter ? openrouterKey : k === 'OPENGATEWAY_API_KEY' && openGateway ? openGatewayKey : k === 'ANTHROPIC_API_KEY' && anthropic ? anthropicKey : env[k] ?? (options.readKeyCache ?? defaultReadKeyCache)(k);
2009
- const hostFallbackNames = (options.fallbackConfig ?? getUserConfig)().llm.fallbackChain;
2449
+ const hostKey = (k: string): string | undefined => k === 'OPENROUTER_API_KEY' && openrouterCredentialRequired ? openrouterKey : k === 'OPENGATEWAY_API_KEY' && openGateway ? openGatewayKey : k === 'ANTHROPIC_API_KEY' && anthropic ? anthropicKey : env[k] ?? (options.readKeyCache ?? defaultReadKeyCache)(k);
2450
+ const hostConfig = (options.fallbackConfig ?? getUserConfig)();
2451
+ const hostFallbackNames = hostConfig.llm.fallbackChain;
2452
+ const zaiOverage = zaiLaunchOverage ?? zaiSettingsFromConfig(hostConfig.llm).overage;
2010
2453
  const normalizedFallback = normalizeFallbackChain(hostFallbackNames);
2011
2454
  let fallbackSteps = normalizedFallback.chain;
2012
2455
  let zaiFallbackSpec: ZaiPodSpec | undefined;
@@ -2058,7 +2501,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2058
2501
  ? Object.fromEntries(codexCredentials.map((candidate, i) => [`codex-${i}.json`, JSON.stringify({ elanousAuth: candidate.elanousAuth, codexAuth: candidate.codexAuth })]))
2059
2502
  : { 'elanous-auth.json': (creds as ReturnType<typeof hostCredentials>).elanousAuth, 'codex-auth.json': (creds as ReturnType<typeof hostCredentials>).codexAuth }),
2060
2503
  'gh-token': podGhToken, feature: input.feature,
2061
- 'llm-fallback-config.json': JSON.stringify({ llm: { fallbackChain: fallbackChain.steps } }),
2504
+ 'llm-fallback-config.json': JSON.stringify({ llm: { fallbackChain: fallbackChain.steps, zaiCodingPlan: { overage: zaiOverage } } }),
2062
2505
  ...(goalDocument !== undefined ? { 'goal-doc': goalDocument } : {}),
2063
2506
  ...Object.fromEntries(Object.entries(skillEnvs).map(([n, text]) => [`skillenv-${n}`, text])),
2064
2507
  ...(grounding ? { [`env-${GROUNDING_TOKEN_ENV}`]: grounding.token } : {}),
@@ -2125,6 +2568,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2125
2568
  }
2126
2569
  }
2127
2570
  if (failedExisting) {
2571
+ captureFailedJob(liveChildRunId);
2128
2572
  const pods = kubectl(['-n', namespace, 'get', 'pods', '-l', `job-name=${name}`, '-o', 'jsonpath={range .items[*]}{.metadata.creationTimestamp}{"\\t"}{range .status.containerStatuses[?(@.name=="child")]}{.state.terminated.reason}{"\\t"}{.state.terminated.exitCode}{end}{"\\n"}{end}']);
2129
2573
  const latestPod = pods.status === 0 ? pods.stdout.trimEnd().split('\n').map((line) => line.split('\t'))
2130
2574
  .filter(([created]) => created && !Number.isNaN(Date.parse(created)))
@@ -2313,7 +2757,22 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2313
2757
  debug.log('self-implement.pod', fit.fitted ? 'grok-deadline-fitted' : 'grok-deadline-kept', { job: name, requestedSeconds: jobDeadlineSeconds, deadlineSeconds: fit.deadlineSeconds, availableSeconds: fit.availableSeconds, relay: Boolean(credentialRelay), marginMs: POD_GROK_EXPIRY_MARGIN_MS });
2314
2758
  jobDeadlineSeconds = fit.deadlineSeconds;
2315
2759
  }
2316
- const job = podJobManifest({ name, namespace, fallbackChain, ...(resultMarkerLine ? { resultMarkerLine } : {}), image: jobImage, workIdentity, ...(env.ELANOUS_TASK_BOUND_PR && /^[1-9]\d*$/.test(env.ELANOUS_TASK_BOUND_PR) && Number.isSafeInteger(Number(env.ELANOUS_TASK_BOUND_PR)) ? { boundPrNumber: Number(env.ELANOUS_TASK_BOUND_PR) } : {}), ...(member?.imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}), repoUrl, ...(options.source ? { source: options.source } : {}), ...(options.repairCommitOnly ? { repairCommitOnly: options.repairCommitOnly } : {}), ...(hostMirror ? { hostMirror } : {}), args: jobArgs, passEnv: jobPassEnv, deadlineSeconds: jobDeadlineSeconds, ...(goalDoc ? { goalDoc } : env.ELANOUS_POD_AUTHOR_ON_POD === '1' && !shard ? { authorSentence: true, ...(env.ELANOUS_POD_AUTHOR_GRADE === 'lite' || env.ELANOUS_POD_AUTHOR_GRADE === 'full' ? { authorGrade: env.ELANOUS_POD_AUTHOR_GRADE } : {}) } : {}), runId: launchRunId, ...(parentRunId ? { parentRunId } : {}), ...(jobArmEnv ? { armEnv: jobArmEnv } : {}), hostId: resolveHostId(env), skillEnvs: Object.keys(skillEnvs), memoryLimit, memoryRequest, imageCommit: jobImageCommit, ...(grok ? { grokCredential: 'grokAuth' in creds && creds.grokAuth ? 'subscription' as const : 'api_key' as const } : {}), ...(codexAccounts ? { codexAccounts } : {}), ...(hostRefresh ? { appCredential: true } : {}), execution: { key: executionKey, attempt: oomRetried ? 2 : 1 }, hostLeaseAdmitted: !!releaseAdmission, ...(roleCodexCredential ? { reviewerCodexAuth: true } : {}), ...(bedrockSpec ? { bedrock: bedrockSpec } : {}), ...(zaiSpec ? { zai: zaiSpec } : {}), ...(zaiFallbackSpec ? { zaiFallback: zaiFallbackSpec } : {}) }); // kubectl 주입(=시험)이면 docker 를 부르지 않는다
2760
+ // POD-LAUNCH-PROVENANCE — fail-closed: a si-task Job without its ★ identity is never applied.
2761
+ let provenance: PodProvenanceMetadata;
2762
+ try {
2763
+ let version: string | undefined;
2764
+ try { version = packageVersion(); } catch { version = undefined; }
2765
+ provenance = podProvenanceMetadata(siTaskPodProvenance({ env, runId: launchRunId, ...(parentRunId ? { parentRunId } : {}), workIdentity,
2766
+ requestedAt: new Date((options.now ?? Date.now)()).toISOString(), feature: input.feature, launchHost: resolveHostId(env),
2767
+ commit: seedSource.kind === 'commit' ? seedSource.sha : seedCommit ?? jobImageCommit, ...(version ? { version } : {}),
2768
+ ...(memoryRequest ? { memReq: memoryRequest } : {}) }));
2769
+ } catch (error) {
2770
+ const message = error instanceof Error ? error.message : String(error);
2771
+ debug.log('pod.provenance', 'si-task-refused', { job: name, reason: message }, { level: 'warn' });
2772
+ cleanupSecret();
2773
+ return { exitCode: 1, output: '', error: { code: 'pod-provenance', message } };
2774
+ }
2775
+ const job = podJobManifest({ provenance, name, namespace, fallbackChain, ...(resultMarkerLine ? { resultMarkerLine } : {}), image: jobImage, workIdentity, ...(env.ELANOUS_TASK_BOUND_PR && /^[1-9]\d*$/.test(env.ELANOUS_TASK_BOUND_PR) && Number.isSafeInteger(Number(env.ELANOUS_TASK_BOUND_PR)) ? { boundPrNumber: Number(env.ELANOUS_TASK_BOUND_PR) } : {}), ...(member?.imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}), repoUrl, ...(options.source ? { source: options.source } : {}), ...(options.repairCommitOnly ? { repairCommitOnly: options.repairCommitOnly } : {}), ...(hostMirror ? { hostMirror } : {}), args: jobArgs, passEnv: jobPassEnv, deadlineSeconds: jobDeadlineSeconds, ...(goalDoc ? { goalDoc } : env.ELANOUS_POD_AUTHOR_ON_POD === '1' && !shard ? { authorSentence: true, ...(env.ELANOUS_POD_AUTHOR_GRADE === 'lite' || env.ELANOUS_POD_AUTHOR_GRADE === 'full' ? { authorGrade: env.ELANOUS_POD_AUTHOR_GRADE } : {}) } : {}), runId: launchRunId, ...(parentRunId ? { parentRunId } : {}), ...(jobArmEnv ? { armEnv: jobArmEnv } : {}), hostId: resolveHostId(env), skillEnvs: Object.keys(skillEnvs), memoryLimit, memoryRequest, imageCommit: jobImageCommit, ...(grok ? { grokCredential: 'grokAuth' in creds && creds.grokAuth ? 'subscription' as const : 'api_key' as const } : {}), ...(codexAccounts ? { codexAccounts } : {}), ...(hostRefresh ? { appCredential: true } : {}), execution: { key: executionKey, attempt: oomRetried ? 2 : 1 }, hostLeaseAdmitted: !!releaseAdmission, ...(roleCodexCredential ? { reviewerCodexAuth: true } : {}), ...(bedrockSpec ? { bedrock: bedrockSpec } : {}), ...(zaiSpec ? { zai: zaiSpec } : {}), ...(zaiFallbackSpec ? { zaiFallback: zaiFallbackSpec } : {}) }); // kubectl 주입(=시험)이면 docker 를 부르지 않는다
2317
2776
  const carriedBranch = goalDoc ? 'goal-doc' as const : env.ELANOUS_POD_AUTHOR_ON_POD === '1' && !shard ? 'author' as const : undefined;
2318
2777
  const carriedChildLlm = carriedBranch ? podDelegatedChildLlm(jobArgs) : undefined;
2319
2778
  jobApplyStartedAt = Date.now();
@@ -2322,6 +2781,11 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2322
2781
  if (a.status !== 0) { cleanupSecret(); return { exitCode: 1, output: a.stderr, error: { code: 'pod-apply', message: a.stderr.trim() } }; }
2323
2782
  releaseAdmission?.applied?.(name, context, namespace);
2324
2783
  const imageFacts = { job: name, childRunId: liveChildRunId, image: jobImage, imageCommit: jobImageCommit, context };
2784
+ // POD-LAUNCH-PROVENANCE — append-only «started» row: who · why · when, on the parent's existing run ledger.
2785
+ const startedRow = { job: name, run: launchRunId, attempt: oomRetried ? 2 : 1, provenance: provenanceRowFromAnnotations(provenance.annotations) };
2786
+ try { appendRunLedgerEntry({ runId: ledgerRunId, event: POD_LAUNCH_STARTED_EVENT, timestamp: new Date((options.now ?? Date.now)()).toISOString(), data: startedRow }, ledgerDir); }
2787
+ catch (error) { debug.log('pod.provenance', 'history-unavailable', { job: name, row: 'started', reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
2788
+ debug.log('pod.provenance', 'job-started', startedRow);
2325
2789
  try { appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-job-image', data: imageFacts }, ledgerDir); }
2326
2790
  catch (error) { debug.log('self-implement.pod', 'job-image-ledger-unavailable', { job: name, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
2327
2791
  debug.log('self-implement.pod', 'job-image', imageFacts);
@@ -2380,6 +2844,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2380
2844
  }
2381
2845
  try {
2382
2846
  appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-child-run', data: { childRunId: liveChildRunId, job: name, attempt: oomRetried ? 2 : 1, ...(goalFile ? { goalFile } : {}) } }, ledgerDir);
2847
+ appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-job-applied', data: { job: name, childRunId: liveChildRunId, jobUid: lifecycleJobUid, host: member?.sshHost ?? resolveHostId(env), context, namespace } }, ledgerDir);
2383
2848
  if (lastPlacement) appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-placement', data: { job: name, childRunId: liveChildRunId, attempt: oomRetried ? 2 : 1, member: lastPlacement.member, kind: lastPlacement.kind, outcome: lastPlacement.outcome, preferred: lastPlacement.preferred, reason: lastPlacement.reason } }, ledgerDir);
2384
2849
  appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-memory-selected', data: { job: name, childRunId: liveChildRunId, attempt: oomRetried ? 2 : 1, limit: memoryLimit, ...(appliedMemoryRequest ? { request: appliedMemoryRequest } : {}), tier: oomRetried ? retryTier : memoryTier, source: oomRetried ? 'oom-retry' : memorySource, reason: oomRetried ? 'OOMKilled' : memoryReason, declaredGoalType: selectedGoalType } }, ledgerDir);
2385
2850
  if (oomRetried && retryFromChildRunId) appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-oom-retry', data: { attempt: 2, from: retryFromMemoryLimit ?? selectedMemoryLimit, to: memoryLimit, tier: retryTier, reason: 'OOMKilled', job: name, childRunId: liveChildRunId, fromChildRunId: retryFromChildRunId } }, ledgerDir);
@@ -2391,15 +2856,18 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2391
2856
  }
2392
2857
  writePodFragment({ spaceId: input.spaceId, context, namespace, job: name, inboxDir: POD_CONTROL_INBOX_DIR, ...(liveChildRunId === normalizeRunId(liveChildRunId) ? { runId: liveChildRunId } : {}), ...(parentRunId && normalizeRunId(parentRunId) === parentRunId ? { parentRunId } : {}) }, env);
2393
2858
  recorded = true;
2394
- debug.log('self-implement.pod', 'job-applied', { job: name, namespace, ...(member ? { context: member.context } : {}), image: jobImage, spaceId: input.spaceId, passEnv, ...(openrouter ? { openrouterKeyPresent: passEnv.includes('OPENROUTER_API_KEY') } : {}), ...(openGateway ? { openGatewayKeyPresent: passEnv.includes('OPENGATEWAY_API_KEY') } : {}), ...(anthropic ? { anthropicKeyPresent: passEnv.includes('ANTHROPIC_API_KEY') } : {}), ...(bedrockSpec ? { bedrockSecret: bedrockSpec.secretName, bedrockSecretKeys: bedrockSpec.keys, bedrockRegionFrom: bedrockSpec.region.from } : {}), ...(zaiRoute ? { zaiRoute, ...(zaiSpec ? { zaiSecret: zaiSpec.secretName } : {}) } : {}), extraArgs: options.extraArgs ?? [], ...(options.armEnv?.ELANOUS_ARM_ID ? { armId: options.armEnv.ELANOUS_ARM_ID } : {}) });
2859
+ debug.log('self-implement.pod', 'job-applied', { job: name, namespace, ...(member ? { context: member.context } : {}), image: jobImage, spaceId: input.spaceId, passEnv, ...(openrouterCredentialRequired ? { openrouterKeyPresent: passEnv.includes('OPENROUTER_API_KEY') } : {}), ...(openGateway ? { openGatewayKeyPresent: passEnv.includes('OPENGATEWAY_API_KEY') } : {}), ...(anthropic ? { anthropicKeyPresent: passEnv.includes('ANTHROPIC_API_KEY') } : {}), ...(bedrockSpec ? { bedrockSecret: bedrockSpec.secretName, bedrockSecretKeys: bedrockSpec.keys, bedrockRegionFrom: bedrockSpec.region.from } : {}), ...(zaiRoute ? { zaiRoute, ...(zaiSpec ? { zaiSecret: zaiSpec.secretName } : {}) } : {}), extraArgs: options.extraArgs ?? [], ...(options.armEnv?.ELANOUS_ARM_ID ? { armId: options.armEnv.ELANOUS_ARM_ID } : {}) });
2395
2860
  let state: 'complete' | 'failed' | 'aborted' = 'failed';
2396
2861
  let failedReason = '';
2397
2862
  let containerReason: string | null = null;
2398
2863
  let containerExitCode: number | null = null;
2864
+ // JOB-FAILURE-TERMINATION-CAPTURE — this Job's Kubernetes facts, read once at the failure verdict (before GC).
2865
+ // Observation only: failedReason · containerReason · containerExitCode keep driving the classification.
2866
+ let podTermination: PodTermination | null = null;
2399
2867
  // METER-POD-ROLLUP-GAP — abort 때 Job 삭제 «전»에 거둔 로그 사본(삭제 뒤엔 PARTIAL 줄도 못 읽는다).
2400
2868
  let abortLogs = '';
2401
2869
  // ⭐ 런 «도중» 원장 증분 회수 — 호스트 슈퍼바이저가 Pod 걸음을 실시간으로 본다(🅣 요청 · 힐 루프 runtime 성형의 전제).
2402
- const follower = createPodLedgerFollower({ runId: liveChildRunId, dir: ledgerDir, exec: (script) => kubectl(['-n', namespace, 'exec', `job/${name}`, '-c', 'child', '--', 'sh', '-c', script]) });
2870
+ const follower = createPodLedgerFollower({ runId: liveChildRunId, dir: ledgerDir, now, exec: (script) => kubectl(['-n', namespace, 'exec', `job/${name}`, '-c', 'child', '--', 'sh', '-c', script]) });
2403
2871
  // ⭐ METER-POD-ROLLUP-GAP-3 — 런 «도중» 마지막 PARTIAL 줄을 호스트가 쥔다: Pod 가 지워지면(SIGTERM 등) 종료 로그에서 PARTIAL 이 사라진다.
2404
2872
  let livePartialLine: string | null = null;
2405
2873
  let pollsSinceCapture = 0;
@@ -2428,6 +2896,10 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2428
2896
  const capturePolls = Math.max(1, options.usagePartialCapturePolls ?? Math.round(POD_USAGE_PARTIAL_CAPTURE_MS / Math.max(1, options.pollMs ?? 15_000)));
2429
2897
  const markerWait = markerToken ? await import('./pod-result-marker-wait.js') : null;
2430
2898
  const waitStartedAt = Date.now();
2899
+ let consecutiveJobNotFound = 0;
2900
+ let podVanished = false;
2901
+ let unmeasurableSince: number | null = null;
2902
+ let unmeasurableIncidentRecorded = false;
2431
2903
  for (;;) {
2432
2904
  if (input.signal?.aborted) {
2433
2905
  recordPodImage(true);
@@ -2442,15 +2914,48 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2442
2914
  }
2443
2915
  if (!imageSnapshotRecorded) recordPodImage(false);
2444
2916
  const g = kubectl(['-n', namespace, 'get', 'job', name, '-o', 'jsonpath={.status.conditions[*].type}']);
2917
+ if (g.status !== 0) {
2918
+ // A transport/5xx error says nothing about Job existence. Only Kubernetes Job NotFound counts.
2919
+ const jobNotFound = /\(NotFound\)/i.test(g.stderr) || /\bjobs?(?:\.batch)?\b[^\n]*\bnot found\b/i.test(g.stderr);
2920
+ consecutiveJobNotFound = jobNotFound ? consecutiveJobNotFound + 1 : 0;
2921
+ unmeasurableSince = null;
2922
+ if (consecutiveJobNotFound >= 3) {
2923
+ state = 'failed';
2924
+ failedReason = 'JobNotFound';
2925
+ podVanished = true;
2926
+ const facts = { job: name, childRunId: liveChildRunId, class: 'pod-vanished', consecutiveNotFound: consecutiveJobNotFound };
2927
+ debug.log('self-implement.pod', 'pod-vanished', facts, { level: 'error' });
2928
+ try {
2929
+ recordRunStop({ runId: ledgerRunId, class: 'pod-vanished', site: 'pod-job-poll',
2930
+ cause: `Job ${name} NotFound on ${consecutiveJobNotFound} consecutive polls`,
2931
+ evidenceRef: `run-ledger:${ledgerRunId}:pod-job-applied`,
2932
+ nextMove: 'Inspect Job deletion and child artifacts before relaunch' },
2933
+ (entry) => appendRunLedgerEntry(entry, ledgerDir));
2934
+ } catch (error) {
2935
+ debug.log('self-implement.pod', 'pod-vanished-ledger-unavailable', { ...facts, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' });
2936
+ }
2937
+ break;
2938
+ }
2939
+ await sleep(options.pollMs ?? 15_000);
2940
+ continue;
2941
+ }
2942
+ consecutiveJobNotFound = 0;
2445
2943
  const types = g.stdout;
2446
2944
  if (/Complete|SuccessCriteriaMet/.test(types)) { releaseAdmission?.observed?.(); state = 'complete'; break; }
2447
2945
  if (/Failed|FailureTarget/.test(types)) {
2448
2946
  releaseAdmission?.observed?.();
2449
2947
  state = 'failed';
2450
- const reason = kubectl(['-n', namespace, 'get', 'job', name, '-o', 'jsonpath={.status.conditions[?(@.type=="Failed")].reason}']);
2451
- failedReason = reason.status === 0 ? reason.stdout.trim() : '';
2948
+ // One Job read serves both: column one is the classification reason, all three the capture (no re-read a GC could empty).
2949
+ let jobRead: { status: number | null; stdout: string } | null = null;
2950
+ let jobReadError: unknown;
2951
+ try { jobRead = kubectl(['-n', namespace, 'get', 'job', name, '-o', `jsonpath=${POD_TERMINATION_JOB_JSONPATH}`]); }
2952
+ catch (error) { jobReadError = error; }
2953
+ failedReason = jobRead?.status === 0 ? (jobRead.stdout.split('\t')[0] ?? '').trim() : '';
2954
+ // One Pods read serves both: its first three columns are the classification layout, the rest the termination capture.
2955
+ let podsRead: { status: number | null; stdout: string } | null = null;
2452
2956
  try {
2453
- const pods = kubectl(['-n', namespace, 'get', 'pods', '-l', `job-name=${name}`, '-o', 'jsonpath={range .items[*]}{.metadata.creationTimestamp}{"\\t"}{range .status.containerStatuses[?(@.name=="child")]}{.state.terminated.reason}{"\\t"}{.state.terminated.exitCode}{end}{"\\n"}{end}']);
2957
+ const pods = kubectl(['-n', namespace, 'get', 'pods', '-l', `job-name=${name}`, '-o', `jsonpath=${POD_TERMINATION_PODS_JSONPATH}`]);
2958
+ podsRead = pods;
2454
2959
  if (pods.status === 0 && pods.stdout) {
2455
2960
  const rows = pods.stdout.trimEnd().split('\n').map((line) => line.split('\t'));
2456
2961
  const latest = rows.filter(([created]) => created && !Number.isNaN(Date.parse(created)))
@@ -2463,6 +2968,16 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2463
2968
  }
2464
2969
  } catch { /* Pod 가 사라졌거나 조회 불가 — 종료 사유는 미상. */ }
2465
2970
  debug.log('self-implement.pod', 'container-terminated', { job: name, container: 'child', reason: containerReason, exitCode: containerExitCode, jobReason: failedReason || null, memoryLimit });
2971
+ podTermination = readPodTermination({ kubectl, namespace, job: name, path: 'job-failed-condition', jobRead, podsRead, jobUid: lifecycleJobUid });
2972
+ // Keep the verdict's single Job read authoritative; collecting files may re-read a Job that GC has removed.
2973
+ captureFailedJob(liveChildRunId);
2974
+ if (jobReadError !== undefined) {
2975
+ // Today's outcome stays (the throw ends the spawn as pod-error); the capture is in the ledger first.
2976
+ try { appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-child-evidence', data: { childEvidence: null, childRunId: liveChildRunId, job: name, podTermination } }, ledgerDir); }
2977
+ catch (error) { debug.log('self-implement.pod', 'child-evidence-write-failed', { job: name, childRunId: liveChildRunId, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
2978
+ debug.log('self-implement.pod', 'pod-termination-captured', { job: name, podTermination, jobReadThrew: true }, { level: 'warn' });
2979
+ throw jobReadError;
2980
+ }
2466
2981
  break;
2467
2982
  }
2468
2983
  // TA-POD-WARMBOOT-PARITY: the condition is not there yet — the child may already be done (k3d condition lag).
@@ -2474,6 +2989,8 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2474
2989
  state = seen === 0 ? 'complete' : 'failed';
2475
2990
  // The child's own rc (the Pod's terminated state may lag like the condition) — exit-code paths read it as before.
2476
2991
  if (seen !== 0) containerExitCode = seen;
2992
+ if (seen !== 0) podTermination = readPodTermination({ kubectl, namespace, job: name, path: 'result-marker', jobUid: lifecycleJobUid });
2993
+ if (seen !== 0) captureFailedJob(liveChildRunId);
2477
2994
  debug.log('self-implement.pod', 'result-marker-early', { job: name, waitedMs: Date.now() - waitStartedAt, rc: seen });
2478
2995
  break;
2479
2996
  }
@@ -2501,7 +3018,19 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2501
3018
  debug.log('self-implement.pod', 'gh-token-refresh-failed', { job: name, reason: 'mint-verification-or-exec-failed' });
2502
3019
  }
2503
3020
  }
2504
- try { follower?.poll(); } catch (e) { debug.log('self-implement.pod', 'ledger-live-unavailable', { job: name, reason: e instanceof Error ? e.message : String(e) }); }
3021
+ let measurement = { ledgerRead: false, activityMeasured: false };
3022
+ try { measurement = follower.poll(); } catch (e) { debug.log('self-implement.pod', 'ledger-live-unavailable', { job: name, reason: e instanceof Error ? e.message : String(e) }); }
3023
+ if (!measurement.ledgerRead && !measurement.activityMeasured) {
3024
+ unmeasurableSince ??= now();
3025
+ const unmeasurableMs = Math.max(0, now() - unmeasurableSince);
3026
+ if (!unmeasurableIncidentRecorded && unmeasurableMs >= 120 * 60_000) {
3027
+ unmeasurableIncidentRecorded = true;
3028
+ const incident = { job: name, childRunId: liveChildRunId, unmeasurableMs, jobStatus: 'alive', ledgerRead: false, activityMeasured: false };
3029
+ try { appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-stall-unmeasurable-incident', data: incident }, ledgerDir); }
3030
+ catch (error) { debug.log('self-implement.pod', 'pod-stall-incident-ledger-unavailable', { job: name, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
3031
+ debug.log('self-implement.pod', 'pod-stall-unmeasurable-incident', incident, { level: 'warn' });
3032
+ }
3033
+ } else unmeasurableSince = null;
2505
3034
  if (++pollsSinceCapture >= capturePolls) {
2506
3035
  pollsSinceCapture = 0;
2507
3036
  try {
@@ -2720,32 +3249,52 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2720
3249
  }
2721
3250
  let noResultDiagnostic: string | null = null;
2722
3251
  // POD-NORESULT: a terminal Job without a parsed disposition is incomplete even when the Job condition says Complete.
2723
- const noResultCandidate = !disposition && !oomKilled && failedReason !== 'DeadlineExceeded' &&
3252
+ const noResultCandidate = !podVanished && !disposition && !oomKilled && failedReason !== 'DeadlineExceeded' &&
2724
3253
  (state === 'complete' || (state === 'failed' && containerExitCode !== 0));
2725
3254
  if (noResultCandidate) {
2726
3255
  try { noResultDiagnostic = podNoResultDiagnostic(readFileSync(runLedgerPath(liveChildRunId, ledgerDir), 'utf8')); }
2727
3256
  catch { /* ledger not returned: do not invent a stage */ }
2728
3257
  noResultDiagnostic ??= 'child terminal result missing; last ledger stage=unknown; round=unknown; mustFix=unknown';
2729
3258
  }
2730
- // POD-NORESULT-LOG-TAIL: the Job runs with backoffLimit 0, so the failed Pod — and its container log —
2731
- // is collected right after this. When the child ledger cannot even name the stage, read the tail here,
2732
- // before deletion, so the parent keeps a maskable reason instead of `childEvidence=null`.
3259
+ // POD-FAIL-EVIDENCE-CAPTURE: failed Pods are collected/deleted immediately after terminal handling.
3260
+ // Preserve a bounded log tail for every non-OOM/non-deadline failure, including known child stages:
3261
+ // the terminal result line can be followed by the useful stderr that explains the actual exit.
2733
3262
  let noResultLogEvidence: Record<string, unknown> | null = null;
2734
- if (noResultDiagnostic?.includes('stage=unknown')) {
3263
+ const shouldCaptureFailureLog = state === 'failed' && !oomKilled && failedReason !== 'DeadlineExceeded';
3264
+ if (shouldCaptureFailureLog || noResultDiagnostic?.includes('stage=unknown')) {
3265
+ const missingStage = noResultDiagnostic?.includes('stage=unknown') ?? false;
2735
3266
  const logRead = readPodNoResultLogTail({ run: kubectl, namespace, job: name, maxLines: POD_NO_RESULT_LOG_TAIL_LINES });
2736
3267
  if ('tail' in logRead) {
2737
- noResultLogEvidence = { stderr: logRead.tail.join('\n'), logTail: logRead.tail, logTailLines: logRead.tail.length };
2738
- debug.log('self-implement.pod', 'no-result-log-tail', { job: name, childRunId: liveChildRunId, diagnostic: noResultDiagnostic, lines: logRead.tail.length, logTail: logRead.tail }, { compact: { stringMax: 500 } });
3268
+ noResultLogEvidence = missingStage
3269
+ ? { stderr: logRead.tail.join('\n'), logTail: logRead.tail, logTailLines: logRead.tail.length }
3270
+ : { podLogTail: logRead.tail, podLogTailLines: logRead.tail.length };
3271
+ debug.log('self-implement.pod', missingStage ? 'no-result-log-tail' : 'failed-pod-log-tail', {
3272
+ job: name, childRunId: liveChildRunId, diagnostic: noResultDiagnostic, lines: logRead.tail.length, logTail: logRead.tail,
3273
+ }, { compact: { stringMax: 500 } });
2739
3274
  } else {
2740
- noResultLogEvidence = { stderr: `log-unavailable: ${logRead.unavailable}` };
2741
- debug.log('self-implement.pod', 'no-result-log-tail', { job: name, childRunId: liveChildRunId, diagnostic: noResultDiagnostic, logUnavailable: logRead.unavailable }, { level: 'warn' });
3275
+ noResultLogEvidence = missingStage
3276
+ ? { stderr: `log-unavailable: ${logRead.unavailable}` }
3277
+ : { podLogUnavailable: logRead.unavailable };
3278
+ debug.log('self-implement.pod', missingStage ? 'no-result-log-tail' : 'failed-pod-log-tail', {
3279
+ job: name, childRunId: liveChildRunId, diagnostic: noResultDiagnostic, logUnavailable: logRead.unavailable,
3280
+ }, { level: 'warn' });
2742
3281
  }
2743
3282
  }
2744
3283
  if (childFailure) debug.log('self-implement.pod', 'child-error', { job: name, stage: childFailure.stage, error: childFailure.error, ...(childPrFailure ?? {}) }, { compact: { stringMax: 500 } });
2745
3284
  if (noResultDiagnostic && !noResultDiagnostic.includes('stage=unknown')) {
2746
3285
  debug.log('self-implement.pod', 'no-result-ledger-diagnostic', { job: name, childRunId: liveChildRunId, diagnostic: noResultDiagnostic }, { compact: { stringMax: 500 } });
2747
3286
  }
3287
+ // POD-LAUNCH-PROVENANCE — append-only «finished» row paired with its started row on job·run.
3288
+ // Keyed on launchRunId like the started row; a reattached Job's inherited child run rides along as childRun.
3289
+ // exitCode stays null when the container's code was not read (never filled in as 0).
3290
+ const finishedRow = { job: name, run: launchRunId, ...(liveChildRunId !== launchRunId ? { childRun: liveChildRunId } : {}),
3291
+ attempt: oomRetried ? 2 : 1, phase: state, exitCode: containerExitCode,
3292
+ reason: failedReason || containerReason || (state === 'complete' ? 'complete' : 'unknown') };
3293
+ try { appendRunLedgerEntry({ runId: ledgerRunId, event: POD_LAUNCH_FINISHED_EVENT, timestamp: new Date((options.now ?? Date.now)()).toISOString(), data: finishedRow }, ledgerDir); }
3294
+ catch (error) { debug.log('pod.provenance', 'history-unavailable', { job: name, row: 'finished', reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
3295
+ debug.log('pod.provenance', 'job-finished', finishedRow);
2748
3296
  debug.log('self-implement.pod', 'job-finished', { job: name, ...(member ? { context: member.context } : {}), state, containerReason, stage: disposition?.stage ?? null, prUrl: disposition?.prUrl ?? null, childRunId: liveChildRunId, ledgerCompleteness,
3297
+ ...(podTermination ? { podTermination } : {}),
2749
3298
  ...(state === 'failed' && !oomKilled && failedReason !== 'DeadlineExceeded'
2750
3299
  ? { ...(childFailure ? { childStage: childFailure.stage } : disposition?.stage ? { childStage: disposition.stage } : {}),
2751
3300
  // POD-NORESULT: a parsed result row without error text is not a missing result line (UX 10-07 01:01: 28 of 28 exit-2 Jobs printed one).
@@ -2820,7 +3369,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2820
3369
  appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-oom-retry-finished', data: { job: name, executionKey, fromChildRunId: retryFromChildRunId, childRunId: liveChildRunId, state } }, ledgerDir);
2821
3370
  }
2822
3371
  if (goalFile) {
2823
- const noResultOutcome = state === 'failed' && containerExitCode !== null && noResultDiagnostic ? 'abandoned' : 'pod-no-result';
3372
+ const noResultOutcome = podVanished || (state === 'failed' && containerExitCode !== null && noResultDiagnostic) ? 'abandoned' : 'pod-no-result';
2824
3373
  const record: GoalExecutionRecord = {
2825
3374
  runId: liveChildRunId,
2826
3375
  stage: (state === 'aborted' || !disposition ? 'aborted' : disposition.stage) as GoalExecutionRecord['stage'],
@@ -2837,8 +3386,10 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2837
3386
  if (state === 'failed' || state === 'aborted' || noResultLogEvidence || (state === 'complete' && noResultDiagnostic !== null)) {
2838
3387
  // Every terminal attempt writes a marker, including one with no parseable child facts.
2839
3388
  // The next attempt must never inherit an earlier Pod's classification.
2840
- const childEvidence = noResultLogEvidence ?? disposition?.childEvidence ?? null;
2841
- try { appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-child-evidence', data: { childEvidence, childRunId: liveChildRunId, job: name } }, ledgerDir); }
3389
+ const childEvidence = noResultLogEvidence
3390
+ ? { ...(disposition?.childEvidence ?? {}), ...noResultLogEvidence }
3391
+ : disposition?.childEvidence ?? null;
3392
+ try { appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-child-evidence', data: { childEvidence, childRunId: liveChildRunId, job: name, ...(podTermination ? { podTermination } : {}) } }, ledgerDir); }
2842
3393
  catch (error) { debug.log('self-implement.pod', 'child-evidence-write-failed', { job: name, childRunId: liveChildRunId, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
2843
3394
  }
2844
3395
  let prLookup: OpenPrByBranch | undefined;
@@ -2846,7 +3397,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2846
3397
  if (disposition && (state === 'failed' || state === 'aborted' || disposition.ok === false)
2847
3398
  && (!Number.isSafeInteger(disposition.prNumber) || (disposition.prNumber ?? 0) <= 0) && terminalBranch) {
2848
3399
  try { prLookup = await (options.lookupOpenPrByBranch ?? defaultLookupOpenPrByBranch)(terminalBranch, repoUrl); }
2849
- catch { prLookup = { status: 'failed', reason: 'pr-lookup-threw' }; }
3400
+ catch (error) { prLookup = { status: 'failed', reason: `pr-lookup-failed: ${error instanceof Error ? error.message : String(error)}`.slice(0, 200) }; }
2850
3401
  }
2851
3402
  const terminalDisposition = prLookup?.status === 'found' && disposition
2852
3403
  ? { ...disposition, prNumber: prLookup.prNumber, prUrl: prLookup.prUrl } : disposition;
@@ -2888,11 +3439,14 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2888
3439
  const finishedDisposition = deadlineExceeded && disposition
2889
3440
  ? { ...disposition, failureClassification: 'run-deadline-exceeded' as const }
2890
3441
  : disposition;
2891
- const podExitCode = state === 'complete' && disposition?.stage !== 'host-regate-failed' && disposition?.ok !== false ? 0 : 1;
3442
+ // A gate environment deficit can exit the child with 1 while still yielding a
3443
+ // verified repair bundle. The host rejudges the new PR head after CAS apply.
3444
+ const podExitCode = podVanished ? 1 : repairPodExitCode({ state, repairCommitOnly: !!options.repairCommitOnly,
3445
+ repairArtifactReady, disposition });
2892
3446
  // SCHED-CONTRACT: a failed child must say why outside the Pod, not «no error diagnostic».
2893
3447
  const rawPodReason = podExitCode === 1 ? extractPodFailureReason({ logs, logTailReason, containerReason, jobReason: failedReason || undefined, deadlineSeconds, result: disposition ? { stage: disposition.stage, prUrl: disposition.prUrl ?? null, prNumber: disposition.prNumber ?? null } : null }) : undefined;
2894
3448
  const podReason = rawPodReason
2895
- ? noResultLogEvidence ? boundedPodLogText(rawPodReason, POD_NO_RESULT_LOG_REASON_MAX_CHARS) : rawPodReason
3449
+ ? redactSecretText(noResultLogEvidence ? boundedPodLogText(rawPodReason, POD_NO_RESULT_LOG_REASON_MAX_CHARS) : rawPodReason)
2896
3450
  : undefined;
2897
3451
  if (podReason) debug.log('self-implement.pod', 'failure-reason', { job: name, reason: podReason });
2898
3452
  if (podExitCode === 1) {
@@ -2904,7 +3458,9 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2904
3458
  return {
2905
3459
  exitCode: podExitCode,
2906
3460
  output: tail,
2907
- ...(deadlineExceeded
3461
+ ...(podVanished
3462
+ ? { error: { code: 'pod-vanished', message: `Job ${name} disappeared after 3 consecutive NotFound polls` } }
3463
+ : deadlineExceeded
2908
3464
  ? { error: { code: 'pod-deadline-exceeded', message: `Job ${name} 이 수명 상한 ${deadlineSeconds}초에 닿았다` } }
2909
3465
  : state === 'failed'
2910
3466
  ? containerExitCode === 7
@@ -3016,6 +3572,10 @@ function podPrOpenFailure(ledger: Record<string, unknown> | undefined | null, te
3016
3572
  }
3017
3573
 
3018
3574
  function lastPodChildFailure(logs: string): { stage: string; error: string; rawError: string } | null {
3575
+ // Keep canonical duplicate-cap attribution while preserving the raw terminal error
3576
+ // used by PR-open failure diagnosis below.
3577
+ const attribution = podChildFailureAttribution(logs);
3578
+ if (!attribution) return null;
3019
3579
  for (const line of logs.split('\n').reverse()) {
3020
3580
  if (!line.trimStart().startsWith('{')) continue;
3021
3581
  let result: unknown;
@@ -3025,10 +3585,8 @@ function lastPodChildFailure(logs: string): { stage: string; error: string; rawE
3025
3585
  const terminal = podTerminalRow(row);
3026
3586
  if (!terminal) continue;
3027
3587
  if (terminal.ok !== false || typeof terminal.error !== 'string') return null;
3028
- const lines = redactSecretText(terminal.error).replace(/\/home\/[^/\s]+\//g, '~/').split(/\r\n|\n|\r/);
3029
- const first = lines[0]!.slice(0, 240);
3030
- const last = lines.at(-1)!.slice(0, 240);
3031
- return { stage: terminal.stage, error: first === last ? first : `${first}\n${last}`, rawError: terminal.error };
3588
+ if (terminal.stage !== attribution.stage) return null;
3589
+ return { ...attribution, error: redactSecretText(attribution.error), rawError: terminal.error };
3032
3590
  }
3033
3591
  return null;
3034
3592
  }