elanous 0.2.23 → 0.2.25

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 (688) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/chunks/1027.a0f04663d24c2779.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/1038.676007ae505822e7.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/1113.a37eadf7f740d955.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/112-fa7c526c20ce1405.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/1144.5fa1fa1d48dbcd12.js +36 -0
  8. package/apps/pwa/out/_next/static/chunks/1146.80d15ea7c6e6ee95.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/1157.f08bf2b70915e176.js +148 -0
  10. package/apps/pwa/out/_next/static/chunks/119b405e.8fdd0e44d1ff37aa.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/1296.04560d98e73fbf51.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/1314-f604ba013ec535b0.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/1454-37fd16f479f5554a.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/1464.f3209af0d8ae4ff2.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/{8532.a86e99acf9b761cb.js → 1516.83dd2c1b86b4e507.js} +2 -2
  16. package/apps/pwa/out/_next/static/chunks/{89849432.9eaa2f4c9c010965.js → 16e57f67.3ea19e893461e611.js} +3 -3
  17. package/apps/pwa/out/_next/static/chunks/1729-d73dd25e564679d4.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/{1505.5826671109111e75.js → 1743.563d3168e95825ef.js} +2 -2
  19. package/apps/pwa/out/_next/static/chunks/1843.5924c24295243261.js +27 -0
  20. package/apps/pwa/out/_next/static/chunks/1987.e28149724b92918b.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/2066-5ca18ffa5a4be68b.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/2240.d10a66695155fd93.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/2391.e2cbc98d4abbb41a.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/2528.3868070ef1be69f9.js +24 -0
  25. package/apps/pwa/out/_next/static/chunks/2566-734cb83bbd405886.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/2697.76ca2ac3efef6f3b.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/275.0e6bad414729bfa0.js +32 -0
  28. package/apps/pwa/out/_next/static/chunks/2803-33bdae771855a8d1.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/2846.d6a433b8663819a5.js +309 -0
  30. package/apps/pwa/out/_next/static/chunks/3143.257997a5f1ed79df.js +34 -0
  31. package/apps/pwa/out/_next/static/chunks/{5967-c0a80f8ffe5d4050.js → 3259-86f3be56e43eaf6a.js} +1 -1
  32. package/apps/pwa/out/_next/static/chunks/3280-e177c33bfbdcfb0a.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/3294-2b6a6d45a46a6c1c.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/3309.364573ceedbdfd5f.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/3522.758840fc5e7fe630.js +287 -0
  36. package/apps/pwa/out/_next/static/chunks/3525-a7c989c1a661e880.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/3694.b1a66387756458c0.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/370.124f9e678a76fc01.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/3797-9643105335e660fe.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/{5578.ac963634373b28c6.js → 4084.15ecaf4240bd9e77.js} +2 -2
  41. package/apps/pwa/out/_next/static/chunks/4089-b3382b8843114eee.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/{8438.ee7f0df3b3f6fcb4.js → 4204.a409ab6250c0f6a8.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/4293.928a8d573c16aab7.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/4402.f9238a3b18a7bd43.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/4490-9b090b0f9427f902.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/4548.13502d0ca3b4f862.js +8 -0
  47. package/apps/pwa/out/_next/static/chunks/4565-67e4c620597965c8.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/{7905.b230b9d06cb4ad5d.js → 4593.10f5b9bd702d4a6c.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/4599-548bc96cda44a5a7.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/4625.206574e33a9e7ae8.js +104 -0
  51. package/apps/pwa/out/_next/static/chunks/4862-392c9a71f0d8dfb4.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/5054.2906d1b02542edc5.js +149 -0
  53. package/apps/pwa/out/_next/static/chunks/{8569.b853a8daa8f31d12.js → 5081.a240b63fad28663a.js} +1 -1
  54. package/apps/pwa/out/_next/static/chunks/5102-c822bbbee5a022a4.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/5194-8c93f2e7e4b6f717.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/5305-bacd7eca0b9a5848.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/5311.9071ee3f44fd7ff5.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/5369.9f34353442daa421.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/{2528.0af5c0e1fad88df6.js → 5408.377c8c543635b81f.js} +1 -1
  60. package/apps/pwa/out/_next/static/chunks/{9502-32c5dc22e8f120f5.js → 5492-e05e26a033ba80d2.js} +3 -3
  61. package/apps/pwa/out/_next/static/chunks/5785-ea8d2c633b6688e3.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/589-49d730cb8138b397.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/5904-264a6412ae3c9028.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/5980.728531d5c566535c.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/599-a914cc0468f56143.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/6011.f525002e21bef0ac.js +23 -0
  67. package/apps/pwa/out/_next/static/chunks/6132.4381ab93683f1acf.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/6149.2e698635e454d8f6.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/{5974.360f8441eb0ac1ec.js → 6196.34d6b229079e9e28.js} +1 -1
  70. package/apps/pwa/out/_next/static/chunks/6356-0afafc4230cf9767.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/6406.64756073f7df35b8.js +77 -0
  72. package/apps/pwa/out/_next/static/chunks/6479-16e62aedafa0e310.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/6599.b5246b46a2453329.js +64 -0
  74. package/apps/pwa/out/_next/static/chunks/{ce383d63.d2ede338788ed18e.js → 65ebff95.081daeff622d238e.js} +1 -1
  75. package/apps/pwa/out/_next/static/chunks/6610.5c26eec011565acb.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/666.37d18474edc1c552.js +2 -0
  77. package/apps/pwa/out/_next/static/chunks/6904.a0b5b1ed4f2d2c20.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/{83cfc5c8.349535543c1548b6.js → 6def8a8e.c81f9ca41028eccf.js} +1 -1
  79. package/apps/pwa/out/_next/static/chunks/7247-1f4aba94aba50d9f.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/7274-e71a7c5b7760cd02.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/7290.1fcaeef3adb5d0d4.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/7477-2c64e42760f82f33.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/{2721.bc9a8f36bd85c07a.js → 7483.ba01c6c209b81bba.js} +1 -1
  84. package/apps/pwa/out/_next/static/chunks/7505-47002b76ff1aef6c.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/7600-11884ca4da68e5d9.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/7607.40d8bb3689697ccb.js +62 -0
  87. package/apps/pwa/out/_next/static/chunks/{6702.ebbd165d5e4a4a14.js → 7752.5cbdd0b433c54af9.js} +1 -1
  88. package/apps/pwa/out/_next/static/chunks/7763.f51fcc19ca1414ec.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/7785.846140c859b1adf6.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/7807-a0fdac6393e0bf36.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/7837-20a63b4b7e9342b1.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/800.7815414cf280545d.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/{8925-2abfb48af477fb7f.js → 8085-1e5fce59500a2cc2.js} +1 -1
  94. package/apps/pwa/out/_next/static/chunks/8100.415f2105faa63e97.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/8290.701aa42ed13aa767.js +226 -0
  96. package/apps/pwa/out/_next/static/chunks/{b4e12462-5fab4a813a63dadd.js → 83a80514-b897b64df43ce108.js} +1 -1
  97. package/apps/pwa/out/_next/static/chunks/8444.d9c74214a3145a43.js +93 -0
  98. package/apps/pwa/out/_next/static/chunks/8569.98c915534928c7f9.js +24 -0
  99. package/apps/pwa/out/_next/static/chunks/{8264.00e434fbd02e9311.js → 8578.dff22d988cc2b106.js} +3 -3
  100. package/apps/pwa/out/_next/static/chunks/8591.b1c7f876e5ec7cfb.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/8715-f63756021d3ab989.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/8879.776c2ec7732aa114.js +115 -0
  103. package/apps/pwa/out/_next/static/chunks/8881.ddca15b3c82c490c.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/8946.5da5c87f32a2e1a8.js +41 -0
  105. package/apps/pwa/out/_next/static/chunks/9117.080b9b75be90e14d.js +74 -0
  106. package/apps/pwa/out/_next/static/chunks/9167.f3ecb28f367d6e69.js +94 -0
  107. package/apps/pwa/out/_next/static/chunks/9224.66095370ee28c93b.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/9238-0682b013c83d6449.js +1 -0
  109. package/apps/pwa/out/_next/static/chunks/{7518.49161e8f5814abdb.js → 9311.24c8f5ea793253dd.js} +1 -1
  110. package/apps/pwa/out/_next/static/chunks/{9976.8f28864317639fe6.js → 94.4cfed21c0b781a06.js} +3 -3
  111. package/apps/pwa/out/_next/static/chunks/9449.d3ed795da4befb1c.js +1 -0
  112. package/apps/pwa/out/_next/static/chunks/949.1e7d45ccb71580aa.js +90 -0
  113. package/apps/pwa/out/_next/static/chunks/9638-d268329f2c6f4f8d.js +1 -0
  114. package/apps/pwa/out/_next/static/chunks/{0e3acd34.d42b2ef87fdf4234.js → 973cbf1b.3bbcf2b41548eca7.js} +1 -1
  115. package/apps/pwa/out/_next/static/chunks/9842-d6644eb53d3dc6bf.js +1 -0
  116. package/apps/pwa/out/_next/static/chunks/9863.94ca3263e760696e.js +1 -0
  117. package/apps/pwa/out/_next/static/chunks/{6571-926ddf024a6ad5df.js → 9917-9e26abc5f7772735.js} +1 -1
  118. package/apps/pwa/out/_next/static/chunks/9938.b23e4d4dfa0114cd.js +79 -0
  119. package/apps/pwa/out/_next/static/chunks/{740.90b31555290236a9.js → 9974.e77b4ff4884557b3.js} +2 -2
  120. package/apps/pwa/out/_next/static/chunks/{4c5e95ce.83e9b16add6fcd8c.js → ab4d57c2.e90bc2cb31949045.js} +7 -7
  121. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d9babd8795f396a5.js +1 -0
  122. package/apps/pwa/out/_next/static/chunks/app/approvals/page-9360b1285b2a346e.js +1 -0
  123. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f08f6529d09ffae1.js +1 -0
  124. package/apps/pwa/out/_next/static/chunks/app/board/page-f000d54d258f9ef6.js +1 -0
  125. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-ad3bd8084048bee4.js → page-b12149c51f7d04b1.js} +1 -1
  126. package/apps/pwa/out/_next/static/chunks/app/bots/page-22f674e7e4c2c9d5.js +1 -0
  127. package/apps/pwa/out/_next/static/chunks/app/ceo/page-8888733e40cea6d1.js +1 -0
  128. package/apps/pwa/out/_next/static/chunks/app/chat/page-9717ec6964b92276.js +1 -0
  129. package/apps/pwa/out/_next/static/chunks/app/consult/page-44eaad486bc98a45.js +1 -0
  130. package/apps/pwa/out/_next/static/chunks/app/control/page-97fa6cf3d34c7eb9.js +1 -0
  131. package/apps/pwa/out/_next/static/chunks/app/decisions/page-305cd294e754906e.js +1 -0
  132. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-a648bc464c9dc754.js → page-dea4b795741126fd.js} +1 -1
  133. package/apps/pwa/out/_next/static/chunks/app/editor/page-35e05d7450461a67.js +1 -0
  134. package/apps/pwa/out/_next/static/chunks/app/exec/page-45889e7070d11126.js +1 -0
  135. package/apps/pwa/out/_next/static/chunks/app/field/page-16a2e893f83ae4d0.js +1 -0
  136. package/apps/pwa/out/_next/static/chunks/app/healthz/route-d9babd8795f396a5.js +1 -0
  137. package/apps/pwa/out/_next/static/chunks/app/inside/page-b2638013c0bf7dfd.js +1 -0
  138. package/apps/pwa/out/_next/static/chunks/app/intake/page-56888bc744e08dac.js +1 -0
  139. package/apps/pwa/out/_next/static/chunks/app/layout-c44f93a41167536f.js +1 -0
  140. package/apps/pwa/out/_next/static/chunks/app/live/page-952c6ceec6d9de38.js +1 -0
  141. package/apps/pwa/out/_next/static/chunks/app/live-run/page-6d0202af568f4c63.js +1 -0
  142. package/apps/pwa/out/_next/static/chunks/app/loops/page-1b810474507f3702.js +1 -0
  143. package/apps/pwa/out/_next/static/chunks/app/market/page-74366ae2693a7d95.js +1 -0
  144. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-4fb6fc34e20c07b7.js +1 -0
  145. package/apps/pwa/out/_next/static/chunks/app/morning/page-e9502921f4f01b1f.js +1 -0
  146. package/apps/pwa/out/_next/static/chunks/app/not-found-b6169ea2f09cf287.js +1 -0
  147. package/apps/pwa/out/_next/static/chunks/app/observatory/page-6a14333d1b1b3967.js +1 -0
  148. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-8c91a9a66c9ec90a.js +1 -0
  149. package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-48fb31810693c357.js → page-4e634c5b98fd4e93.js} +1 -1
  150. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-ed3070d637850548.js +1 -0
  151. package/apps/pwa/out/_next/static/chunks/app/outputs/{page-cb154acadb025414.js → page-b9cc8a10bc6078b4.js} +1 -1
  152. package/apps/pwa/out/_next/static/chunks/app/page-3d3fc73dd187ed52.js +1 -0
  153. package/apps/pwa/out/_next/static/chunks/app/reflection/page-128458c8d86fbb5f.js +1 -0
  154. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-9c04dac0831c2bbb.js +1 -0
  155. package/apps/pwa/out/_next/static/chunks/app/sessions/page-cf0c2b0183991d09.js +1 -0
  156. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-04de9ca6d52a3893.js +1 -0
  157. package/apps/pwa/out/_next/static/chunks/app/settings/page-62527677bbc2fdc4.js +1 -0
  158. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-9a2e09c4a311764c.js +1 -0
  159. package/apps/pwa/out/_next/static/chunks/app/setup/layout-d9babd8795f396a5.js +1 -0
  160. package/apps/pwa/out/_next/static/chunks/app/setup/page-bfeb35d69e8b24bb.js +1 -0
  161. package/apps/pwa/out/_next/static/chunks/app/share/page-89cb33bced824e4a.js +1 -0
  162. package/apps/pwa/out/_next/static/chunks/app/showroom/page-b008aff96be56654.js +1 -0
  163. package/apps/pwa/out/_next/static/chunks/app/tasks/page-814394dcbae789b1.js +1 -0
  164. package/apps/pwa/out/_next/static/chunks/app/term/page-6a87325c87f09dd2.js +1 -0
  165. package/apps/pwa/out/_next/static/chunks/app/today/page-6445ac4cbb8dd869.js +1 -0
  166. package/apps/pwa/out/_next/static/chunks/app/trace/page-852170303c6cd755.js +1 -0
  167. package/apps/pwa/out/_next/static/chunks/app/vault/page-087ceeb975ef272b.js +1 -0
  168. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-97c7361cc2a19319.js +1 -0
  169. package/apps/pwa/out/_next/static/chunks/app/workflows/page-20162174b77f96cf.js +1 -0
  170. package/apps/pwa/out/_next/static/chunks/app/workspace/page-8c6901833f897496.js +1 -0
  171. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-05a4b2c9b91034aa.js +1 -0
  172. package/apps/pwa/out/_next/static/chunks/dcf24383-2f82e35f2d827393.js +1 -0
  173. package/apps/pwa/out/_next/static/chunks/{53746282.1be36038d1abd949.js → e117f801.e92bf10242653497.js} +1 -1
  174. package/apps/pwa/out/_next/static/chunks/e87d89b9.2e71c0ae475d28a1.js +122 -0
  175. package/apps/pwa/out/_next/static/chunks/{0673cc54.8e2ef1c23e49e190.js → f4dfbfc0.a69fb12f854882aa.js} +1 -1
  176. package/apps/pwa/out/_next/static/chunks/framework-bf135b997155e90e.js +1 -0
  177. package/apps/pwa/out/_next/static/chunks/main-5571537543bea91c.js +1 -0
  178. package/apps/pwa/out/_next/static/chunks/main-app-25c51c7df7f3a442.js +1 -0
  179. package/apps/pwa/out/_next/static/chunks/pages/_app-e3b2c1f5d82abd65.js +1 -0
  180. package/apps/pwa/out/_next/static/chunks/pages/_error-083f2e23e9bc3641.js +1 -0
  181. package/apps/pwa/out/_next/static/chunks/webpack-e94d22b7a028217b.js +1 -0
  182. package/apps/pwa/out/_next/static/css/72becfd42807b50d.css +1 -0
  183. package/apps/pwa/out/_next/static/{rfkMm4movqkqMebBrCXiR → dRS6lEhSRRBr1abbzp7fJ}/_buildManifest.js +1 -1
  184. package/apps/pwa/out/approvals/index.html +1 -1
  185. package/apps/pwa/out/approvals/index.txt +23 -23
  186. package/apps/pwa/out/autopilot/index.html +1 -1
  187. package/apps/pwa/out/autopilot/index.txt +23 -23
  188. package/apps/pwa/out/board/index.html +1 -1
  189. package/apps/pwa/out/board/index.txt +23 -23
  190. package/apps/pwa/out/botlab/index.html +1 -1
  191. package/apps/pwa/out/botlab/index.txt +22 -22
  192. package/apps/pwa/out/bots/index.html +1 -1
  193. package/apps/pwa/out/bots/index.txt +22 -22
  194. package/apps/pwa/out/ceo/index.html +1 -1
  195. package/apps/pwa/out/ceo/index.txt +23 -23
  196. package/apps/pwa/out/chat/index.html +1 -1
  197. package/apps/pwa/out/chat/index.txt +22 -22
  198. package/apps/pwa/out/consult/index.html +1 -1
  199. package/apps/pwa/out/consult/index.txt +23 -23
  200. package/apps/pwa/out/control/index.html +1 -1
  201. package/apps/pwa/out/control/index.txt +23 -23
  202. package/apps/pwa/out/decisions/index.html +1 -1
  203. package/apps/pwa/out/decisions/index.txt +22 -22
  204. package/apps/pwa/out/design-check/index.html +1 -1
  205. package/apps/pwa/out/design-check/index.txt +23 -23
  206. package/apps/pwa/out/editor/index.html +1 -1
  207. package/apps/pwa/out/editor/index.txt +23 -23
  208. package/apps/pwa/out/exec/index.html +1 -1
  209. package/apps/pwa/out/exec/index.txt +23 -23
  210. package/apps/pwa/out/field/index.html +1 -1
  211. package/apps/pwa/out/field/index.txt +23 -23
  212. package/apps/pwa/out/healthz +1 -1
  213. package/apps/pwa/out/index.html +1 -1
  214. package/apps/pwa/out/index.txt +22 -22
  215. package/apps/pwa/out/inside/index.html +1 -1
  216. package/apps/pwa/out/inside/index.txt +22 -22
  217. package/apps/pwa/out/intake/index.html +1 -1
  218. package/apps/pwa/out/intake/index.txt +23 -23
  219. package/apps/pwa/out/live/index.html +1 -1
  220. package/apps/pwa/out/live/index.txt +23 -23
  221. package/apps/pwa/out/live-run/index.html +1 -1
  222. package/apps/pwa/out/live-run/index.txt +23 -23
  223. package/apps/pwa/out/loops/index.html +1 -1
  224. package/apps/pwa/out/loops/index.txt +22 -22
  225. package/apps/pwa/out/market/index.html +1 -1
  226. package/apps/pwa/out/market/index.txt +22 -22
  227. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  228. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  229. package/apps/pwa/out/morning/index.html +1 -1
  230. package/apps/pwa/out/morning/index.txt +23 -23
  231. package/apps/pwa/out/observatory/index.html +1 -1
  232. package/apps/pwa/out/observatory/index.txt +23 -23
  233. package/apps/pwa/out/ops/checklist/index.html +1 -1
  234. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  235. package/apps/pwa/out/ops/release/index.html +1 -1
  236. package/apps/pwa/out/ops/release/index.txt +22 -22
  237. package/apps/pwa/out/ops/seats/index.html +1 -1
  238. package/apps/pwa/out/ops/seats/index.txt +22 -22
  239. package/apps/pwa/out/outputs/index.html +1 -1
  240. package/apps/pwa/out/outputs/index.txt +22 -22
  241. package/apps/pwa/out/reflection/index.html +1 -1
  242. package/apps/pwa/out/reflection/index.txt +23 -23
  243. package/apps/pwa/out/scheduler/index.html +1 -1
  244. package/apps/pwa/out/scheduler/index.txt +23 -23
  245. package/apps/pwa/out/sessions/index.html +1 -1
  246. package/apps/pwa/out/sessions/index.txt +22 -22
  247. package/apps/pwa/out/settings/devices/index.html +1 -1
  248. package/apps/pwa/out/settings/devices/index.txt +23 -23
  249. package/apps/pwa/out/settings/index.html +1 -1
  250. package/apps/pwa/out/settings/index.txt +23 -23
  251. package/apps/pwa/out/setup/done/index.html +1 -1
  252. package/apps/pwa/out/setup/done/index.txt +23 -23
  253. package/apps/pwa/out/setup/index.html +1 -1
  254. package/apps/pwa/out/setup/index.txt +23 -23
  255. package/apps/pwa/out/share/index.html +1 -1
  256. package/apps/pwa/out/share/index.txt +23 -23
  257. package/apps/pwa/out/showroom/index.html +1 -1
  258. package/apps/pwa/out/showroom/index.txt +22 -22
  259. package/apps/pwa/out/tasks/index.html +1 -1
  260. package/apps/pwa/out/tasks/index.txt +23 -23
  261. package/apps/pwa/out/term/index.html +1 -1
  262. package/apps/pwa/out/term/index.txt +23 -23
  263. package/apps/pwa/out/today/index.html +1 -1
  264. package/apps/pwa/out/today/index.txt +23 -23
  265. package/apps/pwa/out/trace/index.html +1 -1
  266. package/apps/pwa/out/trace/index.txt +23 -23
  267. package/apps/pwa/out/vault/index.html +1 -1
  268. package/apps/pwa/out/vault/index.txt +23 -23
  269. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  270. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  271. package/apps/pwa/out/workflows/index.html +1 -1
  272. package/apps/pwa/out/workflows/index.txt +23 -23
  273. package/apps/pwa/out/workspace/index.html +1 -1
  274. package/apps/pwa/out/workspace/index.txt +23 -23
  275. package/apps/pwa/out/worktrees/index.html +1 -1
  276. package/apps/pwa/out/worktrees/index.txt +23 -23
  277. package/catalog/models/openrouter/deepseek-v4-pro-0813.yaml +39 -0
  278. package/catalog/models/openrouter/deepseek-v4.1-flash.yaml +38 -0
  279. package/catalog/models/openrouter/glm-5.3-flash.yaml +35 -0
  280. package/catalog/providers/openrouter.yaml +2 -0
  281. package/graphs/implement-loop.yaml +31 -0
  282. package/graphs/ops/harness-gc.yaml +17 -0
  283. package/graphs/ops/recipes.yaml +3 -0
  284. package/graphs/overlays/README.md +1 -1
  285. package/graphs/overlays/stop-heal-ladder.yaml +105 -0
  286. package/graphs/release/release-loop.yaml +5 -5
  287. package/graphs/steward/recipes.yaml +4 -0
  288. package/graphs/steward/steward.yaml +10 -1
  289. package/package.json +1 -1
  290. package/scripts/add-api-key.sh +1 -1
  291. package/scripts/bench-report.ts +46 -1
  292. package/scripts/ci-typecheck-changed.ts +73 -14
  293. package/scripts/fleet/bedrock-pod-secret.ts +122 -0
  294. package/scripts/fleet/node-setup-linux.sh +97 -0
  295. package/scripts/fleet/node-setup.sh +11 -0
  296. package/scripts/fleet/zai-pod-secret.ts +86 -0
  297. package/scripts/land-global-checks.ts +3 -0
  298. package/scripts/measure-log-diet-2.ts +213 -0
  299. package/scripts/measure-tsc-affected-roots.ts +289 -0
  300. package/scripts/release-loop/auto-approve-node.ts +6 -0
  301. package/scripts/release-loop/gate-closure.ts +286 -0
  302. package/scripts/release-loop/gate-executor.ts +28 -0
  303. package/scripts/release-loop/gate-node.ts +333 -37
  304. package/scripts/release-loop/gate-partial.ts +34 -6
  305. package/scripts/release-loop/ops-upgrade-node.ts +63 -42
  306. package/scripts/release-loop/publish-node.ts +6 -0
  307. package/scripts/release-loop/resume-release.ts +46 -13
  308. package/scripts/release-loop/resume.ts +110 -4
  309. package/scripts/release-loop/unattended-release.ts +22 -7
  310. package/scripts/tsc-baseline-cache.ts +172 -0
  311. package/scripts/usage-rollup.ts +72 -6
  312. package/scripts/ux-sim.ts +30 -1
  313. package/skills/youtube-master/scripts/main.ts +4 -1
  314. package/skills/youtube-master/src/obsidian.ts +9 -0
  315. package/src/agent/run-context.ts +10 -0
  316. package/src/agent-mission/driver.ts +18 -3
  317. package/src/agent-mission/resource-ladder.ts +8 -3
  318. package/src/agent-mission/review-loop.ts +8 -2
  319. package/src/agent-substrate/acp-implementer.ts +146 -0
  320. package/src/agent-substrate/pr-reviewer.ts +60 -13
  321. package/src/agent-substrate/self-review-cli.ts +170 -15
  322. package/src/autopilot/mission-critique.ts +18 -5
  323. package/src/autopilot/mission-feasibility-negotiate.ts +11 -10
  324. package/src/autopilot/mission-trace.ts +12 -1
  325. package/src/autopilot/system-lookback-run.ts +16 -10
  326. package/src/bench/bench.ts +183 -0
  327. package/src/budget/cost-tier-advisor.ts +91 -0
  328. package/src/budget/fetchers/grok.ts +15 -5
  329. package/src/budget/llm-cost.ts +37 -5
  330. package/src/budget/shadow-arms.ts +77 -0
  331. package/src/budget/types.ts +10 -2
  332. package/src/budget/unified-usage.ts +256 -10
  333. package/src/cli/bench-cli.ts +34 -0
  334. package/src/cli/doctor-cli.ts +21 -0
  335. package/src/cli/friendly-errors.ts +113 -0
  336. package/src/cli/guardian-cli.ts +26 -1
  337. package/src/cli/image-cli.ts +101 -0
  338. package/src/cli/intake-cli.ts +3 -1
  339. package/src/cli/log-event-names.ts +4 -0
  340. package/src/cli/memory-cli.ts +182 -0
  341. package/src/cli/model-watch-cli.ts +4 -1
  342. package/src/cli/pr-cli.ts +62 -5
  343. package/src/cli/pr-land-overlap-evidence.ts +160 -12
  344. package/src/cli/pr-land-pod-shadow.ts +54 -0
  345. package/src/cli/release-cli.ts +27 -16
  346. package/src/cli/release-run-if-ready.ts +11 -0
  347. package/src/cli/resource-cli.ts +48 -0
  348. package/src/cli/retro-cli.ts +12 -3
  349. package/src/cli/rfc-status.ts +52 -2
  350. package/src/cli/seat-requests-cli.ts +25 -0
  351. package/src/cli/self-update.ts +104 -0
  352. package/src/cli/tasks-cli.ts +139 -10
  353. package/src/cli/usage-cli.ts +93 -8
  354. package/src/compact/verify-probe.ts +23 -3
  355. package/src/context-card/index.ts +50 -0
  356. package/src/control/control-status.ts +30 -1
  357. package/src/core-turn/run-goal-loop.ts +5 -4
  358. package/src/dashboard/ad-run-setup-bindings.ts +279 -0
  359. package/src/dashboard/index.ts +18 -276
  360. package/src/dashboard/slash-runtime/dashboard-handlers.ts +4 -138
  361. package/src/dashboard/slash-runtime/shell-slash.ts +145 -0
  362. package/src/dashboard/stale-install-check.ts +11 -0
  363. package/src/debug/log-diet.ts +53 -10
  364. package/src/debug/log.ts +19 -1
  365. package/src/domains/morning-essential.ts +18 -3
  366. package/src/domains/release-tool.ts +43 -0
  367. package/src/error-report/report.ts +21 -4
  368. package/src/execution-loop/launch-gate.ts +158 -2
  369. package/src/fleet/host-doctor.ts +375 -0
  370. package/src/github/pr-labels.ts +1 -0
  371. package/src/goals/judge.ts +20 -6
  372. package/src/graph-runner/graph-cli.ts +42 -1
  373. package/src/graph-runner/runner.ts +176 -11
  374. package/src/graph-wizard/archetypes.ts +14 -8
  375. package/src/graph-wizard/generate.ts +77 -1
  376. package/src/grok/usage.ts +23 -2
  377. package/src/grok.ts +13 -7
  378. package/src/grounding/ref-index.ts +253 -0
  379. package/src/harness/dispatch-backpressure.ts +10 -2
  380. package/src/harness/harness-clean.ts +199 -78
  381. package/src/harness/harness-cli-command.ts +176 -52
  382. package/src/harness/harness-gc.ts +331 -0
  383. package/src/harness/harness-stop.ts +82 -10
  384. package/src/harness/harness-substrate-default.ts +142 -7
  385. package/src/harness/launch-registry.ts +6 -0
  386. package/src/harness/worktree-branch-prefix.ts +9 -0
  387. package/src/hq/fence.ts +187 -0
  388. package/src/hq/seats.ts +38 -22
  389. package/src/images/image-ledger.ts +179 -0
  390. package/src/index.ts +110 -186
  391. package/src/instance/child-scope.ts +32 -1
  392. package/src/instance/dev-project.ts +133 -0
  393. package/src/instance/resolve.ts +24 -0
  394. package/src/instance/spawn-elanous.ts +45 -0
  395. package/src/intake-plane/collect-github.ts +7 -2
  396. package/src/intake-plane/lens.ts +46 -4
  397. package/src/intake-plane/ref-prefetch.ts +110 -0
  398. package/src/intake-plane/requirement-funnel.ts +53 -1
  399. package/src/intelligence-map/catalog-feed.ts +119 -0
  400. package/src/intelligence-map/model-catalog.ts +72 -0
  401. package/src/intelligence-map/model-watch-intake.ts +40 -0
  402. package/src/intelligence-map/model-watch-mission.ts +14 -1
  403. package/src/llm/bedrock-eventstream.ts +215 -0
  404. package/src/llm/bedrock-provider.ts +114 -41
  405. package/src/llm/bedrock.ts +174 -28
  406. package/src/llm/open-weight-discipline.ts +21 -0
  407. package/src/llm/openrouter-byok-cost.ts +96 -0
  408. package/src/llm/openrouter-deepseek.ts +151 -0
  409. package/src/llm/openrouter-upstream-pins.config.ts +25 -0
  410. package/src/llm/zai-coding-plan.ts +700 -0
  411. package/src/llm.ts +91 -24
  412. package/src/loops/checker.ts +148 -9
  413. package/src/loops/orchestrator/resync-shadow.ts +34 -7
  414. package/src/maturity/feature-maturity.ts +3 -3
  415. package/src/model-tier/llm-tier-map.ts +9 -5
  416. package/src/models/prompts.ts +2 -1
  417. package/src/mss/logging/log-store.ts +58 -11
  418. package/src/nexus/api/http-server.ts +22 -0
  419. package/src/nexus/api/missions.ts +73 -2
  420. package/src/nexus/api/ops-api.ts +18 -0
  421. package/src/nexus/index.ts +7 -75
  422. package/src/nexus/tools-kind.ts +78 -0
  423. package/src/oauth/codex-account-rotation.ts +43 -3
  424. package/src/oauth/codex-account-store.ts +339 -27
  425. package/src/oauth/codex-quota-policy.ts +33 -4
  426. package/src/oauth/fallback-chain.ts +53 -15
  427. package/src/onboarding/first-run.ts +37 -18
  428. package/src/onboarding/web-first.ts +20 -2
  429. package/src/ops/machine-heartbeat.ts +185 -0
  430. package/src/ops/overview-cache.ts +175 -0
  431. package/src/ops/overview.ts +8 -0
  432. package/src/preview/terminal.ts +15 -5
  433. package/src/project/project-store.ts +20 -0
  434. package/src/prompt-cache/openai.ts +3 -0
  435. package/src/prompt-cache/types.ts +5 -0
  436. package/src/prompt-enhance/enhance.ts +23 -0
  437. package/src/release-loop/auto-start.ts +7 -5
  438. package/src/release-loop/checklist.ts +11 -1
  439. package/src/release-loop/gate-shards.ts +4 -0
  440. package/src/release-loop/light-rc.ts +84 -13
  441. package/src/release-loop/merged-pr-checklist.ts +35 -5
  442. package/src/release-loop/placement.ts +16 -1
  443. package/src/release-loop/rehearsal.ts +1 -1
  444. package/src/requirements/requirement-ledger.ts +536 -0
  445. package/src/retro/daily.ts +5 -5
  446. package/src/seat-loop/duty-ledger.ts +64 -0
  447. package/src/seat-loop/seat-loop.ts +32 -10
  448. package/src/self-dev/decompose.ts +37 -3
  449. package/src/self-dev/dev-cli.ts +44 -10
  450. package/src/self-dev/dev-pipeline.ts +39 -9
  451. package/src/self-dev/draft-residue.ts +27 -5
  452. package/src/self-dev/draft-sweep.ts +151 -0
  453. package/src/self-dev/fabric-decompose-adapter.ts +9 -2
  454. package/src/self-dev/graph-journey-route.ts +6 -0
  455. package/src/self-dev/orchestrate-cli.ts +25 -3
  456. package/src/self-dev/orchestrate.ts +41 -6
  457. package/src/self-dev/run-supervisor.ts +218 -15
  458. package/src/self-dev/run-types.ts +13 -0
  459. package/src/self-dev/supervisor-verdict-edges.ts +1 -0
  460. package/src/self-implement/budget-gate.ts +53 -13
  461. package/src/self-implement/child-context-composition.ts +139 -0
  462. package/src/self-implement/child-llm-preference.ts +13 -5
  463. package/src/self-implement/claude-acp-implement.ts +67 -0
  464. package/src/self-implement/flex-signals.ts +59 -0
  465. package/src/self-implement/frozen-merges.ts +8 -3
  466. package/src/self-implement/gate-cli.ts +29 -10
  467. package/src/self-implement/gate-remote.ts +4 -1
  468. package/src/self-implement/goal-author-decompose.ts +10 -2
  469. package/src/self-implement/goal-author.ts +36 -11
  470. package/src/self-implement/goal-new-paths.ts +42 -0
  471. package/src/self-implement/graph-overlay-condition.ts +28 -0
  472. package/src/self-implement/graph-overlay-yaml.ts +41 -7
  473. package/src/self-implement/graph-templates.ts +33 -1
  474. package/src/self-implement/graph-yaml.ts +56 -0
  475. package/src/self-implement/heal-intake.ts +5 -1
  476. package/src/self-implement/host-regate.ts +124 -30
  477. package/src/self-implement/importer-test-index.ts +3 -1
  478. package/src/self-implement/l8-merge-queue.ts +80 -2
  479. package/src/self-implement/merge-train.ts +19 -0
  480. package/src/self-implement/off-diff-evidence.ts +40 -1
  481. package/src/self-implement/orchestrator.ts +230 -32
  482. package/src/self-implement/premise-review.ts +159 -0
  483. package/src/self-implement/regate-detail.ts +79 -0
  484. package/src/self-implement/review-deferred-to-host.ts +21 -0
  485. package/src/self-implement/rework-budget-judgment.ts +23 -3
  486. package/src/self-implement/running-runs.ts +75 -5
  487. package/src/self-implement/scope-adjacency.ts +23 -0
  488. package/src/self-implement/seams.ts +89 -8
  489. package/src/self-implement/self-implement-cli.ts +9 -4
  490. package/src/self-implement/stop-autoheal.ts +69 -2
  491. package/src/session-runtime/retry-policy.ts +2 -1
  492. package/src/skills/tools/agent.ts +16 -1
  493. package/src/skills/tools/persistent-grounding.ts +11 -1
  494. package/src/skills/tools/shell.ts +33 -1
  495. package/src/steward/launch.ts +5 -4
  496. package/src/steward/rejudge-tick.ts +142 -0
  497. package/src/steward/triage-budget.ts +62 -0
  498. package/src/steward/triage.ts +83 -22
  499. package/src/task-agent/actions.ts +62 -5
  500. package/src/task-agent/board.ts +92 -3
  501. package/src/task-agent/card-evidence.ts +88 -1
  502. package/src/task-agent/completion-evidence.ts +5 -0
  503. package/src/task-agent/cover.ts +166 -3
  504. package/src/task-agent/judge.ts +16 -0
  505. package/src/task-agent/land-order-ledger.ts +95 -0
  506. package/src/task-agent/live-move-history.ts +87 -1
  507. package/src/task-agent/live-moves.ts +350 -20
  508. package/src/task-agent/parent-host.ts +27 -3
  509. package/src/task-agent/rejudge-on-head.ts +223 -24
  510. package/src/task-agent/review-prior-fix.ts +146 -0
  511. package/src/task-agent/review-result-capture.ts +3 -1
  512. package/src/task-agent/run-adopt.ts +136 -0
  513. package/src/task-agent/shadow.ts +65 -9
  514. package/src/task-agent/task-hand.ts +57 -7
  515. package/src/task-orchestrator/surfaces/pod-account-broker.ts +38 -5
  516. package/src/task-orchestrator/surfaces/pod-bedrock-child.ts +74 -0
  517. package/src/task-orchestrator/surfaces/pod-child-default.ts +50 -0
  518. package/src/task-orchestrator/surfaces/pod-child-providers.ts +14 -1
  519. package/src/task-orchestrator/surfaces/pod-command-job.ts +14 -0
  520. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +79 -6
  521. package/src/task-orchestrator/surfaces/pod-host-acp-review.ts +122 -0
  522. package/src/task-orchestrator/surfaces/pod-lease.ts +17 -5
  523. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +11 -5
  524. package/src/task-orchestrator/surfaces/pod-manifest-test-normalize.ts +23 -0
  525. package/src/task-orchestrator/surfaces/pod-pool.ts +73 -4
  526. package/src/task-orchestrator/surfaces/pod-zai-child.ts +101 -0
  527. package/src/task-orchestrator/surfaces/self-implement-pod.ts +432 -35
  528. package/src/task-orchestrator/surfaces/self-implement.ts +19 -1
  529. package/src/typecheck-ratchet.ts +1 -0
  530. package/src/user-config.ts +69 -15
  531. package/src/ux-sim/fault-cred-expiry.ts +100 -0
  532. package/src/ux-sim/fault-pod-death.ts +74 -0
  533. package/src/ux-sim/fault-pool-cap.ts +81 -0
  534. package/src/version/packed-revision.json +1 -1
  535. package/src/webclone/qa-table.ts +22 -0
  536. package/src/workflow-runtime/triggers/registry.ts +59 -0
  537. package/src/workflow-runtime/triggers/webhook-source.ts +35 -5
  538. package/apps/pwa/out/_next/static/chunks/0a09e062-34f747a17d8d8c2e.js +0 -1
  539. package/apps/pwa/out/_next/static/chunks/1168.a0fb3d9db76c00bd.js +0 -77
  540. package/apps/pwa/out/_next/static/chunks/1325-dc3f3c21eb181cb4.js +0 -1
  541. package/apps/pwa/out/_next/static/chunks/1357.a843c41d2cd7ec31.js +0 -34
  542. package/apps/pwa/out/_next/static/chunks/1430.53f49882b17607c2.js +0 -24
  543. package/apps/pwa/out/_next/static/chunks/1459-be6509b3419d8a9f.js +0 -1
  544. package/apps/pwa/out/_next/static/chunks/153.0a4e3cd516930491.js +0 -1
  545. package/apps/pwa/out/_next/static/chunks/1572.7bc291352e241a45.js +0 -1
  546. package/apps/pwa/out/_next/static/chunks/1593-d07ad068c120dfb0.js +0 -1
  547. package/apps/pwa/out/_next/static/chunks/1608.b7c900baa56cab32.js +0 -1
  548. package/apps/pwa/out/_next/static/chunks/1615-4d8156febaac4b35.js +0 -1
  549. package/apps/pwa/out/_next/static/chunks/1646.e03f0fbc26bc72f6.js +0 -1
  550. package/apps/pwa/out/_next/static/chunks/1669-da843ed73aa301f5.js +0 -1
  551. package/apps/pwa/out/_next/static/chunks/176.13ba0659e3e96fd4.js +0 -1
  552. package/apps/pwa/out/_next/static/chunks/1990.9c60f495ef33c070.js +0 -36
  553. package/apps/pwa/out/_next/static/chunks/2042e469.f86158dd5c1e51a1.js +0 -122
  554. package/apps/pwa/out/_next/static/chunks/2543-28ec6aeb80a5e1e1.js +0 -1
  555. package/apps/pwa/out/_next/static/chunks/2561.43c99d6ee914ee6a.js +0 -62
  556. package/apps/pwa/out/_next/static/chunks/2638.b39ab2f674caa1a5.js +0 -1
  557. package/apps/pwa/out/_next/static/chunks/272-b23d2e92161d11fd.js +0 -1
  558. package/apps/pwa/out/_next/static/chunks/2778.67e0c1d484189ccf.js +0 -1
  559. package/apps/pwa/out/_next/static/chunks/2841.10ff309f6a3168af.js +0 -1
  560. package/apps/pwa/out/_next/static/chunks/2886-9639f97d523d752a.js +0 -1
  561. package/apps/pwa/out/_next/static/chunks/2978.57fb87ce21cc5a85.js +0 -1
  562. package/apps/pwa/out/_next/static/chunks/3664.15f968c39d15bd28.js +0 -149
  563. package/apps/pwa/out/_next/static/chunks/3786-935eb3fcde90e496.js +0 -1
  564. package/apps/pwa/out/_next/static/chunks/3813.df4a01ae89c6f63f.js +0 -1
  565. package/apps/pwa/out/_next/static/chunks/3961-fbef064c213b2b18.js +0 -1
  566. package/apps/pwa/out/_next/static/chunks/3992.f79c70480e8c4077.js +0 -309
  567. package/apps/pwa/out/_next/static/chunks/40-413e8641de9ed1e5.js +0 -1
  568. package/apps/pwa/out/_next/static/chunks/4054-521f11a503daed64.js +0 -1
  569. package/apps/pwa/out/_next/static/chunks/4315.e7356f34c7d8ec79.js +0 -1
  570. package/apps/pwa/out/_next/static/chunks/4489-bf629de6f04f7681.js +0 -1
  571. package/apps/pwa/out/_next/static/chunks/4540.557733cc88c6a82b.js +0 -1
  572. package/apps/pwa/out/_next/static/chunks/462.0275b4338c5b6337.js +0 -1
  573. package/apps/pwa/out/_next/static/chunks/4658-7ed9266098a787e4.js +0 -1
  574. package/apps/pwa/out/_next/static/chunks/4945-0f4046cfc3228ea3.js +0 -1
  575. package/apps/pwa/out/_next/static/chunks/5105-18b4165c5d3a86b0.js +0 -1
  576. package/apps/pwa/out/_next/static/chunks/5205-6992210fe31f45f4.js +0 -1
  577. package/apps/pwa/out/_next/static/chunks/5287-72898b126d722457.js +0 -1
  578. package/apps/pwa/out/_next/static/chunks/5313.1697309c532b9856.js +0 -2
  579. package/apps/pwa/out/_next/static/chunks/5474.991b90688c3cb321.js +0 -1
  580. package/apps/pwa/out/_next/static/chunks/5544-871146192d095594.js +0 -1
  581. package/apps/pwa/out/_next/static/chunks/5935.d5e8ca3e096772e8.js +0 -1
  582. package/apps/pwa/out/_next/static/chunks/6006.84e8b418a7a0db97.js +0 -1
  583. package/apps/pwa/out/_next/static/chunks/6013-1267ba7454d6764b.js +0 -1
  584. package/apps/pwa/out/_next/static/chunks/6038-1e0f7ee1086da4cf.js +0 -1
  585. package/apps/pwa/out/_next/static/chunks/617.709dd0019fe99796.js +0 -1
  586. package/apps/pwa/out/_next/static/chunks/6261.fb9bf94947094636.js +0 -74
  587. package/apps/pwa/out/_next/static/chunks/6411.1966866fae31ffa9.js +0 -1
  588. package/apps/pwa/out/_next/static/chunks/6481-b829d99aa8d158f5.js +0 -1
  589. package/apps/pwa/out/_next/static/chunks/6541.2b7daa043100d0fa.js +0 -1
  590. package/apps/pwa/out/_next/static/chunks/6597.2d7a6d77ab485b27.js +0 -64
  591. package/apps/pwa/out/_next/static/chunks/67.5dd2dc543c2fa3de.js +0 -1
  592. package/apps/pwa/out/_next/static/chunks/6777-312496b0d774719b.js +0 -1
  593. package/apps/pwa/out/_next/static/chunks/681.0116f7036cfb4e8d.js +0 -32
  594. package/apps/pwa/out/_next/static/chunks/6871.30b2bf23b3126e36.js +0 -24
  595. package/apps/pwa/out/_next/static/chunks/6882.737d366d54e29d83.js +0 -1
  596. package/apps/pwa/out/_next/static/chunks/7035.54c2c535164b1c9b.js +0 -1
  597. package/apps/pwa/out/_next/static/chunks/7086-8bfa7e8f796a03d9.js +0 -1
  598. package/apps/pwa/out/_next/static/chunks/7126.5e5599f251726b67.js +0 -93
  599. package/apps/pwa/out/_next/static/chunks/7203-47ff0740a293e284.js +0 -1
  600. package/apps/pwa/out/_next/static/chunks/7268.017f49dafb5f0ff3.js +0 -1
  601. package/apps/pwa/out/_next/static/chunks/7355.5d65a87e9567ae08.js +0 -1
  602. package/apps/pwa/out/_next/static/chunks/7392.4018c2a62177f296.js +0 -1
  603. package/apps/pwa/out/_next/static/chunks/7471.3a3d1be50559cfb8.js +0 -148
  604. package/apps/pwa/out/_next/static/chunks/7500.73489a345cef2201.js +0 -226
  605. package/apps/pwa/out/_next/static/chunks/7565.c2f72a15c1ed0396.js +0 -27
  606. package/apps/pwa/out/_next/static/chunks/7651.6d1c984d3d8587a4.js +0 -1
  607. package/apps/pwa/out/_next/static/chunks/7713.7ab863ed59b2d661.js +0 -1
  608. package/apps/pwa/out/_next/static/chunks/7793.d7cd2026767ddd54.js +0 -115
  609. package/apps/pwa/out/_next/static/chunks/7837.c7c267e5dceadb11.js +0 -1
  610. package/apps/pwa/out/_next/static/chunks/7891.09266382daf70d1c.js +0 -1
  611. package/apps/pwa/out/_next/static/chunks/7990-cbf6a01bcbdcb7e6.js +0 -1
  612. package/apps/pwa/out/_next/static/chunks/8119-819c549e0c197786.js +0 -1
  613. package/apps/pwa/out/_next/static/chunks/8186-74ea19a3700d04a8.js +0 -1
  614. package/apps/pwa/out/_next/static/chunks/8267-e7b3262b735ae106.js +0 -1
  615. package/apps/pwa/out/_next/static/chunks/8386.86518e55b75f7cf5.js +0 -8
  616. package/apps/pwa/out/_next/static/chunks/8437.ba6eeeb29590042f.js +0 -23
  617. package/apps/pwa/out/_next/static/chunks/8469-1500be57e582296f.js +0 -1
  618. package/apps/pwa/out/_next/static/chunks/8483-7164cecdaf178ca6.js +0 -1
  619. package/apps/pwa/out/_next/static/chunks/8722-4c940f02c496ad36.js +0 -1
  620. package/apps/pwa/out/_next/static/chunks/887-41d0dcf5af48c628.js +0 -1
  621. package/apps/pwa/out/_next/static/chunks/9039.da86361496797522.js +0 -90
  622. package/apps/pwa/out/_next/static/chunks/905.d5745a25884754d4.js +0 -94
  623. package/apps/pwa/out/_next/static/chunks/9089-d909f3d4478b0628.js +0 -1
  624. package/apps/pwa/out/_next/static/chunks/9096.dc5c7a267f83790c.js +0 -41
  625. package/apps/pwa/out/_next/static/chunks/93.a567257b6bdb7fe4.js +0 -1
  626. package/apps/pwa/out/_next/static/chunks/9320.419acb9665b46597.js +0 -287
  627. package/apps/pwa/out/_next/static/chunks/9356.b8ceda6634612ff8.js +0 -79
  628. package/apps/pwa/out/_next/static/chunks/94.e9e1043b35cd3eed.js +0 -1
  629. package/apps/pwa/out/_next/static/chunks/9400-7498ea90b2e6ee8f.js +0 -1
  630. package/apps/pwa/out/_next/static/chunks/9930-58644af9c813d6e1.js +0 -1
  631. package/apps/pwa/out/_next/static/chunks/9932.367cf4725fedd6e2.js +0 -1
  632. package/apps/pwa/out/_next/static/chunks/9935.1628b904cdc8ce0e.js +0 -104
  633. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-a2aa8709d193ee0d.js +0 -1
  634. package/apps/pwa/out/_next/static/chunks/app/approvals/page-d5341ac96203a983.js +0 -1
  635. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-0160ab83c8685a12.js +0 -1
  636. package/apps/pwa/out/_next/static/chunks/app/board/page-55c631c032dcc47e.js +0 -1
  637. package/apps/pwa/out/_next/static/chunks/app/bots/page-e6784759a2531031.js +0 -1
  638. package/apps/pwa/out/_next/static/chunks/app/ceo/page-5c5d0e370f495ef6.js +0 -1
  639. package/apps/pwa/out/_next/static/chunks/app/chat/page-c325e21543cd48a8.js +0 -1
  640. package/apps/pwa/out/_next/static/chunks/app/consult/page-6168a7c43e6ef8f5.js +0 -1
  641. package/apps/pwa/out/_next/static/chunks/app/control/page-31417d3604154a6d.js +0 -1
  642. package/apps/pwa/out/_next/static/chunks/app/decisions/page-e9606dc0bdec9305.js +0 -1
  643. package/apps/pwa/out/_next/static/chunks/app/editor/page-25bb425afdb27541.js +0 -1
  644. package/apps/pwa/out/_next/static/chunks/app/exec/page-32d72fdef1afe11a.js +0 -1
  645. package/apps/pwa/out/_next/static/chunks/app/field/page-d27b794c8ba76bbb.js +0 -1
  646. package/apps/pwa/out/_next/static/chunks/app/healthz/route-a2aa8709d193ee0d.js +0 -1
  647. package/apps/pwa/out/_next/static/chunks/app/inside/page-2bd9ce9c020d02d4.js +0 -1
  648. package/apps/pwa/out/_next/static/chunks/app/intake/page-a34096245e63b9f2.js +0 -1
  649. package/apps/pwa/out/_next/static/chunks/app/layout-22170f4f00a15496.js +0 -1
  650. package/apps/pwa/out/_next/static/chunks/app/live/page-4cc66d22ca4ec502.js +0 -1
  651. package/apps/pwa/out/_next/static/chunks/app/live-run/page-83b14c729fcea837.js +0 -1
  652. package/apps/pwa/out/_next/static/chunks/app/loops/page-6e2781c523dad761.js +0 -1
  653. package/apps/pwa/out/_next/static/chunks/app/market/page-26dd415c8f13f341.js +0 -1
  654. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-ccdb83ed40c8d724.js +0 -1
  655. package/apps/pwa/out/_next/static/chunks/app/morning/page-7ad3de8fa06fdea9.js +0 -1
  656. package/apps/pwa/out/_next/static/chunks/app/not-found-015c02c4fec4023e.js +0 -1
  657. package/apps/pwa/out/_next/static/chunks/app/observatory/page-6b0ae5b5c42f6d2f.js +0 -1
  658. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-786111f30b6f0969.js +0 -1
  659. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-4e17cce5e2ed0fee.js +0 -1
  660. package/apps/pwa/out/_next/static/chunks/app/page-16258e956ea93fc2.js +0 -1
  661. package/apps/pwa/out/_next/static/chunks/app/reflection/page-c2dd2b19c93f90ab.js +0 -1
  662. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-85f35dd1dd96ae87.js +0 -1
  663. package/apps/pwa/out/_next/static/chunks/app/sessions/page-d3c007c8a0ddd7ee.js +0 -1
  664. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-7837ce2aacb97b04.js +0 -1
  665. package/apps/pwa/out/_next/static/chunks/app/settings/page-7350ee93f4fe1033.js +0 -1
  666. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-00723458575f2b19.js +0 -1
  667. package/apps/pwa/out/_next/static/chunks/app/setup/layout-a2aa8709d193ee0d.js +0 -1
  668. package/apps/pwa/out/_next/static/chunks/app/setup/page-745d9b6c015076f8.js +0 -1
  669. package/apps/pwa/out/_next/static/chunks/app/share/page-736d33c7414a8a1a.js +0 -1
  670. package/apps/pwa/out/_next/static/chunks/app/showroom/page-119be9a0444d2482.js +0 -1
  671. package/apps/pwa/out/_next/static/chunks/app/tasks/page-fa806c018833e332.js +0 -1
  672. package/apps/pwa/out/_next/static/chunks/app/term/page-c782041b5f5199ba.js +0 -1
  673. package/apps/pwa/out/_next/static/chunks/app/today/page-d431fbdea6ad93dd.js +0 -1
  674. package/apps/pwa/out/_next/static/chunks/app/trace/page-0c056abff82b9891.js +0 -1
  675. package/apps/pwa/out/_next/static/chunks/app/vault/page-242d668e4fbf72c4.js +0 -1
  676. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-847c3de6ca8a4416.js +0 -1
  677. package/apps/pwa/out/_next/static/chunks/app/workflows/page-81dd0d8ae923ed72.js +0 -1
  678. package/apps/pwa/out/_next/static/chunks/app/workspace/page-74e69ba2c8e970d4.js +0 -1
  679. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-70038f41ae7cf931.js +0 -1
  680. package/apps/pwa/out/_next/static/chunks/cd39fa05.fac4545f42a3ef70.js +0 -1
  681. package/apps/pwa/out/_next/static/chunks/framework-b2763b10dc6b3836.js +0 -1
  682. package/apps/pwa/out/_next/static/chunks/main-app-27893e93b0ec193d.js +0 -1
  683. package/apps/pwa/out/_next/static/chunks/main-f280047194e1daf8.js +0 -1
  684. package/apps/pwa/out/_next/static/chunks/pages/_app-e90eff2d0b70564c.js +0 -1
  685. package/apps/pwa/out/_next/static/chunks/pages/_error-70f2d654b3435d34.js +0 -1
  686. package/apps/pwa/out/_next/static/chunks/webpack-4794f053bd5e4230.js +0 -1
  687. package/apps/pwa/out/_next/static/css/5619d59e87eddf66.css +0 -1
  688. /package/apps/pwa/out/_next/static/{rfkMm4movqkqMebBrCXiR → dRS6lEhSRRBr1abbzp7fJ}/_ssgManifest.js +0 -0
@@ -28,6 +28,8 @@ import { TA_LAND_ENV } from '../../task-agent/task-hand.js';
28
28
  import { appendRunLedgerEntry, loadRunLedger, runLedgerDir, runLedgerPath } from '../../self-implement/run-ledger.js';
29
29
  import type { GoalExecutionRecord } from '../../self-implement/orchestrator.js';
30
30
  import { runHostRegate, type HostRegateResult } from '../../self-implement/host-regate.js';
31
+ import { resolvePodHostAcpReview, runPodHostAcpReview, type PodHostAcpReviewOutcome } from './pod-host-acp-review.js';
32
+ import { REVIEW_DEFERRED_TO_HOST_ENV, REVIEW_DEFERRED_TO_HOST_MERGE_REASON } from '../../self-implement/review-deferred-to-host.js';
31
33
  import { classifyPodTerminal, applyPodTerminal, podTerminalPrBody, type PrTerminalRecord, type DraftResidueContext } from '../../self-dev/draft-residue.js';
32
34
 
33
35
  export const POD_CONTROL_INBOX_DIR = '/tmp/elanous-control.inbox';
@@ -50,7 +52,7 @@ export const POD_JOB_DEADLINE_SECONDS = 10_800;
50
52
  // 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 ·
51
53
  // p75 6.2Gi · p90 11.7Gi — 23/42 went above the old 4Gi request, so the scheduler over-packed. 6Gi ≈ p75; limits stay.
52
54
  // Node allocatable ≈ 343Gi ≫ 25 × 6Gi. One-minute samples can miss the true peak (lower bound).
53
- import type { PodPlacement, PodPlacementKind, PodPoolMember, PodPoolScheduler } from './pod-pool.js';
55
+ import { podWorkLabels, type PodWorkIdentity, type PodPlacement, type PodPlacementKind, type PodPoolMember, type PodPoolScheduler } from './pod-pool.js';
54
56
  export { POD_CHILD_REQUESTS } from './pod-lease.js';
55
57
  import { measurePoolLease, recommendConcurrency, POD_CHILD_REQUESTS, POD_HOST_LEASE_ANNOTATION, POD_JOB_TTL_AFTER_FINISHED_SECONDS, LEASE_KUBECTL_MAX_BUFFER, memoryQuantityBytes } from './pod-lease.js';
56
58
  import { ACTUAL_SUBSTRATE_ENV, RUN_CONTRACT_ENV, carryRunContract, completionFloorFor } from '../../self-implement/graph-run-contract.js';
@@ -67,9 +69,11 @@ import { enterJourneyNode, exitJourneyNode, passJourneyNode } from '../../self-d
67
69
  import { routeJourneyEdge } from '../../self-dev/graph-journey-route.js';
68
70
  import { selectLiveDetail } from '../../live/detail-switch.js';
69
71
  import { authStorePath } from '../../oauth/store.js';
70
- import { grokAuthFilePath, isGrokSubscriptionExpiring, refreshGrokSubscriptionToken, resolveGrokCredential } from '../../grok/credential.js';
72
+ import { grokAuthFilePath, isGrokSubscriptionExpiring, refreshGrokSubscriptionToken, resolveGrokCredential, type GrokRefreshOutcome } from '../../grok/credential.js';
71
73
  import { defaultGrokModel } from '../../grok/models.js';
72
74
  import { resolveHostId } from '../../platform/host-id.js';
75
+ import { BUDGET_SNAPSHOT_ENV, encodeBudgetSnapshot, harnessLaunchBudgetDecision, type BudgetSnapshot } from '../../execution-loop/launch-gate.js';
76
+ import type { ReadBudgetInputsDeps } from '../../self-implement/budget-gate.js';
73
77
  import { envLiteral } from '../../platform/env-literal.js';
74
78
  import { LLM_TIER_MAP_BY_PROVIDER, lookupLlmTierSpec, type LlmTierProvider } from '../../model-tier/llm-tier-map.js';
75
79
  import { parseSelfImplementJson, type SelfImplementJobDone, type SelfImplementJobSpawn } from './self-implement.js';
@@ -79,8 +83,12 @@ import { declaredGoalType, type GoalType as DeclaredGoalType } from '../../self-
79
83
  import { readPodMemoryAdvice, type PodMemoryAdvice } from '../../cli/pod-memory-advice.js';
80
84
  import { getUserConfig, roleLlmEnvName, type UserConfig } from '../../user-config.js';
81
85
  import { podChildProviderOf, podChildProviderRefusal, type PodChildProvider } from './pod-child-providers.js';
86
+ import { BEDROCK_POD_EGRESS_LABEL, bedrockPodEnv, checkBedrockPodSecret, type BedrockPodSpec } from './pod-bedrock-child.js';
87
+ import { resolveBedrockRegion, validBedrockRegion } from '../../llm/bedrock.js';
88
+ import { ZAI_POD_EGRESS_LABEL, checkZaiPodSecret, countActiveZaiJobs, decideZaiPodSlot, zaiOverflowChildArgs, zaiPodEnv, type ZaiPodSpec } from './pod-zai-child.js';
89
+ import { ZAI_CODING_DEFAULT_MODEL, ZAI_OPENROUTER_FALLBACK_MODEL, zaiSettingsFromConfig, type ZaiCodingPlanSettings } from '../../llm/zai-coding-plan.js';
82
90
  import { selectEnhanceLlm } from '../../prompt-enhance/enhance.js';
83
- import { extractPodFailureReason, podTerminalRow } from './pod-failure-reason.js';
91
+ import { classifyPodExit, extractPodFailureReason, podTerminalRow, type PodExitClass } from './pod-failure-reason.js';
84
92
  import { OLD_DOOR_STAMP_ENV } from '../../self-dev/old-door.js';
85
93
 
86
94
  export type Kubectl = (args: readonly string[], input?: string) => { status: number | null; stdout: string; stderr: string };
@@ -106,6 +114,10 @@ export interface PodSpawnOptions {
106
114
  rotationAccounts?: readonly string[];
107
115
  /** 미지정이면 종전 Codex Job. */
108
116
  provider?: PodChildProvider;
117
+ /** Host launch-gate verdict for this Job's child LLM (test seam) — the Pod gate follows it instead of judging again. Default: the host's own `harness` launch-gate decision (chain ⊕ user caps). */
118
+ budgetSnapshot?: (childLlm: PodBudgetChildLlm) => Promise<BudgetSnapshot | undefined> | BudgetSnapshot | undefined;
119
+ /** Input reader seam; the host still runs its normal budget decision and Job propagation. */
120
+ budgetInputDeps?: ReadBudgetInputsDeps & { readLiveGrok?: () => Promise<number | undefined> };
109
121
  /** Child model/effort chosen at launch (`--child-llm-model/--child-llm-effort`) — carried into the Pod child. */
110
122
  childModel?: string;
111
123
  childEffort?: string;
@@ -138,11 +150,17 @@ export interface PodSpawnOptions {
138
150
  /** Job 수명 상한(초). */
139
151
  deadlineSeconds?: number;
140
152
  pollMs?: number;
153
+ /** METER-POD-ROLLUP-GAP-3 — 런 도중 PARTIAL 표본을 거두는 폴 간격 수(시험 seam · 기본 POD_USAGE_PARTIAL_CAPTURE_MS ÷ pollMs). */
154
+ usagePartialCapturePolls?: number;
141
155
  /** LAUNCH-STALL: ms a launch may wait in one stage before `launch-stalled` (test seam; default harness.launchStallMinutes · 15 min). */
142
156
  launchStallMs?: number;
143
157
  kubectl?: Kubectl;
144
158
  /** Host-side regate (injected for Pod tests). */
145
159
  hostRegate?: (input: { prNumber: number; headCommit: string; repoRoot: string; goalFile?: string; noMerge?: true }) => Promise<HostRegateResult>;
160
+ /** REVIEW-ACP-POD — Pod merge-ready PR 을 호스트 Claude ACP 로 리뷰한 뒤 재게이트(opt-in · 옵션 → env `ELANOUS_POD_HOST_ACP_REVIEW` → config `llm.podHostAcpReview`). */
161
+ hostAcpReview?: boolean;
162
+ /** REVIEW-ACP-POD — 호스트 ACP 리뷰 seam(시험 주입 · 기본 `runPodHostAcpReview`). */
163
+ podHostAcpReview?: (input: { prNumber: number; headCommit: string; repoRoot: string; job?: string }) => Promise<PodHostAcpReviewOutcome>;
146
164
  /**
147
165
  * TA-LIVE-LAND-2 — `taskAgent.liveMoves` (test seam; default reads the user config). Consulted only when the run carries
148
166
  * the `ELANOUS_TA_LAND=1` marker (`tasks hand --ta-land`): with `propose-land` the host re-gates but leaves the merge to
@@ -183,6 +201,11 @@ export interface PodSpawnOptions {
183
201
  /** POD-ANTHROPIC-PROVIDER — anthropic 자식의 API 키(시험 주입). 기본 = 제품 자격 해석 `getAnthropicApiKey()`(키 캐시 → env).
184
202
  * ⛔ kubectl 주입(=시험)이면 실 캐시를 안 읽는다 — env ⊕ readKeyCache 만 본다. */
185
203
  anthropicKey?: () => string | undefined;
204
+ /** BEDROCK-PROVIDER ② — 호스트 리전(시험 주입). 기본 = `resolveBedrockRegion`(env → ~/.aws/config 의 region 한 칸).
205
+ * ⛔ kubectl 주입(=시험)이면 실 ~/.aws 를 안 읽는다 — env 의 AWS_REGION/AWS_DEFAULT_REGION 만 본다. */
206
+ bedrockRegion?: () => string | undefined;
207
+ /** ZAI-POD-CHILD — glm(ZAI) Pod 의 동시 상한·넘침 정책(시험 주입). 기본 = `llm.zaiCodingPlan`(`zaiSettingsFromConfig`). */
208
+ zaiSettings?: () => ZaiCodingPlanSettings;
186
209
  /** 호스트 키 캐시(`~/.cache/<소문자 이름>`)에서 키를 읽는다(시험 주입) — env 에 없을 때. */
187
210
  readKeyCache?: (name: string) => string | undefined;
188
211
  env?: NodeJS.ProcessEnv;
@@ -212,6 +235,52 @@ export function podJobName(spaceId: string): string {
212
235
  return `si-${slug || 'job'}-${hash}`.replace(/-+/g, '-').slice(0, 58);
213
236
  }
214
237
 
238
+ /** The child LLM the Pod's delegated `harness say/ask` will be launched with — the question the host verdict answers. */
239
+ export interface PodBudgetChildLlm { provider?: string; model?: string }
240
+
241
+ /** The one host-side budget verdict a Pod Job carries (BUDGET-SNAPSHOT-TO-POD). Fail-soft: no snapshot means the Pod marks the budget unmeasured. */
242
+ function hostBudgetSnapshot(childLlm: PodBudgetChildLlm, options: PodSpawnOptions, env: NodeJS.ProcessEnv, job: string): Promise<BudgetSnapshot | undefined> | BudgetSnapshot | undefined {
243
+ const unavailable = (error: unknown): undefined => {
244
+ try { debug.log('self-implement.pod', 'budget-snapshot-unavailable', { job, provider: childLlm.provider ?? null, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); } catch { /* observation is fail-soft */ }
245
+ return undefined;
246
+ };
247
+ let measurement: Promise<BudgetSnapshot | undefined> | BudgetSnapshot | undefined;
248
+ try {
249
+ measurement = options.budgetSnapshot ? options.budgetSnapshot(childLlm) : measureHostBudgetSnapshot(childLlm, env, options.budgetInputDeps);
250
+ } catch (error) { return unavailable(error); }
251
+ if (!(measurement instanceof Promise)) return measurement;
252
+ let timer: ReturnType<typeof setTimeout> | undefined;
253
+ const deadline = new Promise<undefined>((resolve) => {
254
+ timer = setTimeout(() => resolve(unavailable(new Error('host budget snapshot timed out after 10s'))), 10_000);
255
+ });
256
+ return Promise.race([measurement, deadline]).catch(unavailable).finally(() => { if (timer) clearTimeout(timer); });
257
+ }
258
+
259
+ /** The host runs the same launch-gate judgement the Pod would (chain ⊕ the user's caps) and ships the verdict, not raw usage to re-judge. */
260
+ async function measureHostBudgetSnapshot(childLlm: PodBudgetChildLlm, env: NodeJS.ProcessEnv, inputDeps?: ReadBudgetInputsDeps & { readLiveGrok?: () => Promise<number | undefined> }): Promise<BudgetSnapshot | undefined> {
261
+ const host = resolveHostId(env);
262
+ const measuredAt = Date.now();
263
+ const { readBudgetInputsLive, CODEX_PROVIDER, GROK_PROVIDER } = await import('../../self-implement/budget-gate.js');
264
+ const inputs = await readBudgetInputsLive(inputDeps);
265
+ const { decision } = harnessLaunchBudgetDecision(inputs, { ...(childLlm.provider ? { childLlmProvider: childLlm.provider } : {}), ...(childLlm.model ? { childLlmModel: childLlm.model } : {}) });
266
+ const provider = decision.provider ?? childLlm.provider ?? 'chain';
267
+ const used = provider === GROK_PROVIDER
268
+ ? inputs.grokUsedPercent
269
+ : provider === CODEX_PROVIDER
270
+ ? (() => {
271
+ const percents = inputs.codexCandidates
272
+ .map((candidate) => candidate.reached === true ? 100 : candidate.usedPercent)
273
+ .filter((percent): percent is number => typeof percent === 'number' && Number.isFinite(percent));
274
+ return percents.length === 0 ? undefined : Math.min(...percents);
275
+ })()
276
+ : undefined;
277
+ return {
278
+ provider, ...(used !== undefined ? { remaining: Math.min(100, Math.max(0, 100 - used)) } : {}), window: 'weekly', measuredAt, host,
279
+ ...(childLlm.provider ? { requested: childLlm.provider } : {}),
280
+ decision: { action: decision.action, ...(decision.provider ? { provider: decision.provider } : {}), ...(decision.model ? { model: decision.model } : {}) },
281
+ };
282
+ }
283
+
215
284
  /** «3시간 안 만료» — 선갱신 대상이라는 표지(다른 자격 오류와 구분한다). */
216
285
  export class PodCodexExpiringError extends Error {
217
286
  constructor(readonly account: string, readonly hoursLeft: number) {
@@ -394,10 +463,16 @@ export type PodGrokSkipReason = 'missing' | 'expiring' | 'refresh-failed';
394
463
 
395
464
  /** GROK-OPTIONAL (10-05) — may this launch plan put grok in the Pod's chain at all?
396
465
  * The Pod copy must outlive the Job (POD_GROK_MIN_VALIDITY_MS), so an expiring subscription that the host cannot
397
- * refresh is «not available» here: the plan drops grok and goes on with codex instead of failing every launch. */
466
+ * refresh is «not available» here: the plan drops grok and goes on with codex instead of failing every launch.
467
+ * AUT-GROK-CRED-EXPIRY-NO-LAUNCH (10-12): the refresh is already attempted once (`grok models`), but
468
+ * `refreshGrokSubscriptionToken` reports failure as a «value», never a throw — so a missing/broken CLI used to be
469
+ * read as `expiring`. The outcome is now classified (`failed`/`no-credential` → `refresh-failed`, `unchanged` →
470
+ * `expiring`) and every unusable verdict also lands as `harness.pod`/`grok-credential-unusable` so the autopsy and
471
+ * the dispatcher can group same-cause no-launches. ⛔ Never switches the provider here (billing path unchanged). */
398
472
  export function podGrokSubscriptionUsable(opts: {
399
473
  home?: string; env?: NodeJS.ProcessEnv;
400
- refresh?: () => void; isExpiring?: () => boolean; expiresAt?: () => string | null;
474
+ /** Test seam — the host refresh. A returned outcome is classified; `void` counts as an attempt that did not fail. */
475
+ refresh?: () => GrokRefreshOutcome | void; isExpiring?: () => boolean; expiresAt?: () => string | null;
401
476
  log?: (category: string, event: string, data: Record<string, unknown>) => void;
402
477
  } = {}): { usable: true } | { usable: false; reason: PodGrokSkipReason; expiresAt: string | null } {
403
478
  const log = opts.log ?? ((category, event, data) => debug.log(category, event, data));
@@ -409,19 +484,35 @@ export function podGrokSubscriptionUsable(opts: {
409
484
  return all.sort().at(-1) ?? null;
410
485
  } catch { return null; }
411
486
  };
412
- const skip = (reason: PodGrokSkipReason) => {
487
+ const skip = (reason: PodGrokSkipReason, refreshOutcome: GrokRefreshOutcome | 'threw' | 'not-attempted') => {
413
488
  const at = expiresAt();
414
- try { log('pod.grok-credentials', 'skipped', { reason, expiresAt: at }); } catch { /* observation is fail-soft */ }
489
+ try { log('pod.grok-credentials', 'skipped', { reason, expiresAt: at, refreshOutcome }); } catch { /* observation is fail-soft */ }
490
+ try { log('harness.pod', 'grok-credential-unusable', { reason, expiresAt: at, refreshOutcome, minValidityMs: POD_GROK_MIN_VALIDITY_MS }); } catch { /* fail-soft */ }
415
491
  return { usable: false as const, reason, expiresAt: at };
416
492
  };
417
493
  const credential = resolveGrokCredential({ home: opts.home, env: opts.env });
418
- if (credential?.kind !== 'subscription') return skip('missing');
494
+ if (credential?.kind !== 'subscription') return skip('missing', 'not-attempted');
419
495
  const expiring = opts.isExpiring ?? (() => isGrokSubscriptionExpiring({ home: opts.home, bufferMs: POD_GROK_MIN_VALIDITY_MS }));
420
496
  if (!expiring()) return { usable: true };
421
- let refreshed = true;
422
- try { (opts.refresh ?? (() => { refreshGrokSubscriptionToken({ home: opts.home }); }))(); } catch { refreshed = false; }
497
+ let outcome: GrokRefreshOutcome | 'threw' = 'unchanged';
498
+ try { outcome = (opts.refresh ?? (() => refreshGrokSubscriptionToken({ home: opts.home })))() ?? 'unchanged'; } catch { outcome = 'threw'; }
423
499
  if (!expiring()) return { usable: true };
424
- return skip(refreshed ? 'expiring' : 'refresh-failed');
500
+ return skip(outcome === 'failed' || outcome === 'no-credential' || outcome === 'threw' ? 'refresh-failed' : 'expiring', outcome);
501
+ }
502
+
503
+ /** AUT-GROK-CRED-EXPIRY-NO-LAUNCH — read-only launch check for dispatchers: no refresh, no CLI spawn, no log.
504
+ * `ready:false` means a Pod launch that names grok will be refused (or drop grok) unless a refresh extends the token. */
505
+ export function grokLaunchReadiness(opts: { home?: string; env?: NodeJS.ProcessEnv; now?: () => number } = {}):
506
+ { ready: true; expiresAt: string | null } | { ready: false; reason: 'missing' | 'expiring'; expiresAt: string | null } {
507
+ const credential = resolveGrokCredential({ home: opts.home, env: opts.env });
508
+ let at: string | null = null;
509
+ try {
510
+ const scopes = JSON.parse(readFileSync(grokAuthFilePath(opts.home), 'utf8')) as Record<string, { expires_at?: unknown }>;
511
+ at = Object.values(scopes).flatMap((s) => (s && typeof s.expires_at === 'string' ? [s.expires_at] : [])).sort().at(-1) ?? null;
512
+ } catch { /* no auth file */ }
513
+ if (credential?.kind !== 'subscription') return { ready: false, reason: 'missing', expiresAt: at };
514
+ const expiring = isGrokSubscriptionExpiring({ home: opts.home, bufferMs: POD_GROK_MIN_VALIDITY_MS, ...(opts.now ? { now: opts.now } : {}) });
515
+ return expiring ? { ready: false, reason: 'expiring', expiresAt: at } : { ready: true, expiresAt: at };
425
516
  }
426
517
 
427
518
  /** The one launch line for a skipped grok copy — the reader sees why grok left this run's chain. */
@@ -449,6 +540,31 @@ function githubRepositoryFromUrl(repoUrl: string): string | null {
449
540
  return owner && name && ![owner, name].some((part) => part === '.' || part === '..') ? `${owner}/${name}` : null;
450
541
  }
451
542
 
543
+ /** GROK-CHILD-GIT-GUARD — the Pod holds a GitHub write token and our repo cannot protect main (private free plan · API 403),
544
+ * so a child tool could `git push origin main`, force-push another run's branch or delete remote branches. A global
545
+ * pre-push hook lets a Pod push only its own run branches (self-impl/* · salvage/* · bench/<id>/<arm>) and refuses
546
+ * everything else, including deletes and the shared bench base. It stops accidental and reset-then-push overwrites;
547
+ * it is not a sandbox — credential isolation from child tools is the next slice. */
548
+ export const POD_GIT_HOOKS_DIR = '$HOME/.elanous-git-hooks';
549
+ export function podGitPushGuardScript(): string {
550
+ return [
551
+ `mkdir -p "${POD_GIT_HOOKS_DIR}" && cat > "${POD_GIT_HOOKS_DIR}/pre-push" <<'ELANOUS_PRE_PUSH'`,
552
+ '#!/bin/sh',
553
+ 'while read -r lref lsha rref rsha; do',
554
+ ' case "$lsha" in *[!0]*) ;; *) echo "[pod] git-guard: refused remote delete $rref" >&2; exit 1 ;; esac',
555
+ ' case "$rref" in',
556
+ ' refs/heads/self-impl/*|refs/heads/salvage/*) ;;',
557
+ ' refs/heads/bench/*/base) echo "[pod] git-guard: refused push to shared bench base $rref" >&2; exit 1 ;;',
558
+ ' refs/heads/bench/*/*) ;;',
559
+ ' *) echo "[pod] git-guard: refused push to $rref (only this run\'s branches)" >&2; exit 1 ;;',
560
+ ' esac',
561
+ 'done',
562
+ 'exit 0',
563
+ 'ELANOUS_PRE_PUSH',
564
+ `chmod 755 "${POD_GIT_HOOKS_DIR}/pre-push" && git config --global core.hooksPath "${POD_GIT_HOOKS_DIR}"`,
565
+ ].join('\n');
566
+ }
567
+
452
568
  /** Job 스크립트의 salvage 단계 — 자식 rc 가 0 이 아닐 때만, 변경·미푸시 커밋이 있는 격리 워크트리를 `salvage/<job>/<worktree>` 로 push.
453
569
  * main·self-impl/ 로는 절대 안 민다. 실패해도 제어 흐름을 바꾸지 않는다(호출자는 이 뒤에 `exit $rc`). */
454
570
  /**
@@ -746,6 +862,10 @@ export function podNamedChildProvider(named: string | undefined, credential: { g
746
862
  if (!childModel?.trim()) return { refuse: 'pod: openrouter 자식 모델 없음 — --child-llm-model 필요' };
747
863
  return { provider: 'openrouter' };
748
864
  }
865
+ // BEDROCK-PROVIDER ② — 자격은 클러스터의 이름 있는 Secret 에서 온다. 있나·키가 갖춰졌나는 Pod 발사 «전»(apply 0)에 판정한다(checkBedrockPodSecret).
866
+ if (named === 'bedrock') return { provider: 'bedrock' };
867
+ // ZAI-POD-CHILD — glm 도 같은 결: 자격은 클러스터의 이름 있는 ZAI Secret · 있나·동시 상한은 Pod 발사 «전»(apply 0)에 판정한다(pod-zai-child.ts).
868
+ if (named === 'glm') return { provider: 'glm' };
749
869
  // anthropic — API 과금(구독 아님). «명시적으로 anthropic» 일 때만 탄다 · 키 없이 띄우지 않는다(fail-closed).
750
870
  if (named !== 'anthropic') return { refuse: podChildProviderRefusal(named) ?? `pod: Pod 자식 provider 판정 불가 — 받음 ${String(named)}` };
751
871
  if (!credential.anthropicKey) return { refuse: 'pod: Pod 에 anthropic 키 없음 — 호스트 키 캐시 또는 ANTHROPIC_API_KEY 필요' };
@@ -757,11 +877,25 @@ export function podFallbackCredentials(named: PodChildProvider | undefined, fall
757
877
  return named === 'openai-codex' ? { grokSubscription: false, grokApiKey: false } : fallback;
758
878
  }
759
879
 
880
+ /** ZAI-POD-CHILD — 이 프로세스가 «ZAI 로 정했지만 아직 apply 안 한» Job 이름(같은 프로세스의 동시 결정이 상한을 넘지 않게). apply·오류 뒤 바로 지운다. */
881
+ const zaiPendingApply = new Set<string>();
882
+
760
883
  /** Child LLM flags for the Pod child — grok always names its model; codex only when the launch named it (10-05 PODPROVIDER). */
761
884
  export function podChildLlmArgs(options: Pick<PodSpawnOptions, 'provider' | 'childModel' | 'childEffort' | 'childProviderExplicit'>): string[] {
762
- const provider = options.provider === 'grok' || options.provider === 'openrouter' || options.provider === 'anthropic' ? options.provider : options.childProviderExplicit ? 'openai-codex' : undefined;
885
+ // BEDROCK-POD-PROVIDER-DROP (10-11) — 명시 발사는 provider 를 «이름으로» 받는다. 이름이 비었거나 목록 밖이면 조용히 codex 로 풀지 않고 거부한다
886
+ // (🩸 bedrock 명시가 podBase 에서 떨어져 이 줄이 openai-codex 로 풀었다 — Pod 자식이 codex(team)로 돌았다). throw = Job apply 전.
887
+ const refusal = options.provider && !podChildProviderOf(options.provider)
888
+ ? podChildProviderRefusal(options.provider)
889
+ : options.childProviderExplicit && !options.provider
890
+ ? 'pod: --child-llm-provider 가 명시됐는데 Pod 옵션에 provider 가 없다 — openai-codex 로 조용히 풀지 않는다'
891
+ : undefined;
892
+ if (refusal) {
893
+ debug.log('self-implement.pod', 'child-provider-refused', { provider: options.provider ?? null, childProviderExplicit: options.childProviderExplicit === true, reason: refusal }, { level: 'warn' });
894
+ throw new Error(refusal);
895
+ }
896
+ const provider = options.provider === 'openai-codex' && !options.childProviderExplicit ? undefined : options.provider;
763
897
  if (!provider) return [];
764
- const model = options.childModel?.trim() || (provider === 'grok' ? defaultGrokModel().id : undefined);
898
+ const model = options.childModel?.trim() || (provider === 'grok' ? defaultGrokModel().id : provider === 'glm' ? ZAI_CODING_DEFAULT_MODEL : undefined);
765
899
  if (provider === 'openrouter' && !model) throw new Error('pod: openrouter 자식 모델 없음 — --child-llm-model 필요');
766
900
  // `self implement` registers no --child-llm-effort — passing it kills the Pod at argument parsing, so effort stays host-only.
767
901
  return ['--child-llm-provider', provider, ...(model ? ['--child-llm-model', model] : [])];
@@ -788,7 +922,38 @@ export function resolvePodOpenRouterReviewer(option: PodOpenRouterReviewer | und
788
922
  return { choice: 'glm', source: 'default', ...(raw ? { rejected: raw } : {}) };
789
923
  }
790
924
 
791
- export function podJobManifest(o: { name: string; namespace: string; image: string; /** 레지스트리 이미지면 IfNotPresent(노드가 pull) · 반입 이미지면 Never. */ imagePullPolicy?: 'Never' | 'IfNotPresent'; repoUrl: string; source?: PodSource; 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 }): Record<string, unknown> {
925
+ /** CHILD-LLM-PIN-POD (0.2.24) — the child LLM pair from Pod args, for the delegated `harness say`/`harness ask` lines.
926
+ * 🩸 Those lines are built from `quotedAsk` (only --base/merge flags), so `--child-llm-*` never reached the Pod child and an
927
+ * openrouter Pod fell back to the provider default model (10-11: runs labelled Luna/Sonnet all ran GLM-5.3).
928
+ * ⛔ Effort is not carried — `podChildLlmArgs` never puts it in args (host-only by design). */
929
+ export function podDelegatedChildLlm(args: readonly string[]): { provider: string; model?: string } | undefined {
930
+ const providerIndex = args.indexOf('--child-llm-provider');
931
+ const provider = providerIndex >= 0 ? args[providerIndex + 1] : undefined;
932
+ if (!provider || provider.startsWith('--')) return undefined;
933
+ const modelIndex = args.indexOf('--child-llm-model');
934
+ const model = modelIndex >= 0 ? args[modelIndex + 1] : undefined;
935
+ return { provider, ...(model && !model.startsWith('--') ? { model } : {}) };
936
+ }
937
+
938
+ /** ⭐ MF-d3b5a70f — Pod 스크립트의 SIGTERM trap 한 줄. 두 겹을 고친다:
939
+ * 🩸 (a) bash 는 foreground 명령(`elanous self implement`, 수시간) 실행 중 받은 trap 을 그 명령이 끝날 때까지
940
+ * 미룬다 ⇒ ~30s grace 안에 발화하지 못하고 SIGKILL 로 넘어갔다. 그래서 메인 명령은 podJobManifest 안에서
941
+ * `… & si_pid=$!` 로 띄우고 `wait "$si_pid"` 로 기다린다 — `wait` 는 trap 에 즉시 끊긴다(grace 내 발화).
942
+ * 🩸 (b) trap 이 TERM 기본 종료를 삼키면 컨테이너 rc 가 143→137(SIGKILL) 로 바뀐다 ⇒ trap 은 PARTIAL 롤업을
943
+ * 짧게(≤8s) 백그라운드로 쏘고 `exit 143` 으로 원래 rc 를 보존한다 — podUsageMissingReason 의 `sigterm-143`
944
+ * 분기가 실물 경로로 도달한다(시험은 합성 exitCode:143 이 아니라 실물 신호로 잰다).
945
+ * ⛔ 롤업 실패는 rc 를 바꾸지 않는다(각 단계 `|| true`). trap 몸통은 짧게 유지한다(골 ①). */
946
+ /** METER-POD-ROLLUP-GAP-2 — 부분 롤업 표본기가 «방금 낸 seq» 를 적는 파일. trap 은 여기에 +1 해 마지막 PARTIAL 이 가장 큰 seq 를 갖게 한다
947
+ * (표본기는 서브셸이라 변수가 trap 에 안 보인다 · 없거나 숫자가 아니면 0 으로 보고 1 을 쓴다). */
948
+ export const POD_USAGE_PARTIAL_SEQ_FILE = '/tmp/si.usage-partial-seq';
949
+
950
+ export function podSigtermTrapScript(seqFile: string = POD_USAGE_PARTIAL_SEQ_FILE): string {
951
+ // ⭐ METER-POD-ROLLUP-GAP-2 — trap PARTIAL 은 `--seq <표본기 seq + 1>` 로 낸다(전엔 seq 가 늘 1 이라 dedup 에서 앞선 표본에 졌다) ·
952
+ // 표본기를 먼저 끊어 trap 뒤에 옛 표본이 «마지막 줄»로 끼어들지 않게 한다.
953
+ 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`;
954
+ }
955
+
956
+ export function podJobManifest(o: { name: string; namespace: string; image: string; workIdentity?: PodWorkIdentity; /** 레지스트리 이미지면 IfNotPresent(노드가 pull) · 반입 이미지면 Never. */ imagePullPolicy?: 'Never' | 'IfNotPresent'; repoUrl: string; source?: PodSource; 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 }): Record<string, unknown> {
792
957
  const quoted = o.args.map((a) => `'${a.replace(/'/g, `'\\''`)}'`).join(' ');
793
958
  const childProviderIndex = o.args.indexOf('--child-llm-provider');
794
959
  const childModelIndex = o.args.indexOf('--child-llm-model');
@@ -801,8 +966,13 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
801
966
  const askBaseIndex = delegated ? o.args.indexOf('--base') : -1;
802
967
  const quotedAsk = (askBaseIndex >= 0 && o.args[askBaseIndex + 1]
803
968
  ? ` '--base' '${o.args[askBaseIndex + 1]!.replace(/'/g, `'\\''`)}'`
804
- : '') + (delegated && o.args.includes('--merge-by-host') ? ' --merge-by-host' : delegated && o.args.includes('--open-pr') && !o.args.includes('--auto-merge') ? ' --no-auto-merge' : '')
969
+ : '') + (delegated && o.args.includes('--merge-by-host') ? " '--merge-by-host'" : delegated && o.args.includes('--open-pr') && !o.args.includes('--auto-merge') ? ' --no-auto-merge' : '')
805
970
  + (delegated && o.args.includes('--no-supervise') ? ' --no-supervise' : '');
971
+ const delegatedChildLlm = delegated ? podDelegatedChildLlm(o.args) : undefined;
972
+ const shellQuote = (value: string) => `'${value.replace(/'/g, `'\\''`)}'`;
973
+ const quotedChildLlm = delegatedChildLlm
974
+ ? ` '--child-llm-provider' ${shellQuote(delegatedChildLlm.provider)}${delegatedChildLlm.model ? ` '--child-llm-model' ${shellQuote(delegatedChildLlm.model)}` : ''}`
975
+ : '';
806
976
  const script = [
807
977
  'set -u',
808
978
  openrouterModel
@@ -811,6 +981,10 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
811
981
  : 'mkdir -p ~/.elanous && export ELANOUS_LLM_PROVIDER=openrouter'
812
982
  : anthropicChild
813
983
  ? 'mkdir -p ~/.elanous && export ELANOUS_LLM_PROVIDER=anthropic'
984
+ : o.bedrock
985
+ ? 'mkdir -p ~/.elanous && export ELANOUS_LLM_PROVIDER=bedrock'
986
+ : o.zai
987
+ ? 'mkdir -p ~/.elanous && export ELANOUS_LLM_PROVIDER=glm'
814
988
  : o.grokCredential === 'subscription'
815
989
  ? 'mkdir -p ~/.grok && install -m 600 /creds/grok-auth.json ~/.grok/auth.json && export ELANOUS_LLM_PROVIDER=grok'
816
990
  : o.grokCredential === 'api_key'
@@ -820,6 +994,7 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
820
994
  // 🔑 스킬 키(.env) — 이미지엔 없다. 이 런의 Secret 에서 각 스킬 폴더로 0600 복사(값은 로그에 안 나온다).
821
995
  ...(o.skillEnvs?.length ? [`for n in ${o.skillEnvs.join(' ')}; do [ -d ~/.claude/skills/$n ] && install -m 600 /creds/skillenv-$n ~/.claude/skills/$n/.env; done; echo "[pod] skill env: ${o.skillEnvs.join(',')}"`] : []),
822
996
  'git config --global user.name "elanous pod child" && git config --global user.email "noreply@anthropic.com" && gh auth setup-git',
997
+ podGitPushGuardScript(),
823
998
  'curl -s -m 3 -o /dev/null http://host.orb.internal:31415/health && { echo "[pod] ISOLATION FAIL"; exit 3; }',
824
999
  podSourceScript(o.source ?? { kind: 'default' }, o.repoUrl),
825
1000
  ...(goalPath ? [
@@ -828,14 +1003,21 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
828
1003
  // Only fixed command shapes leave the Pod; arbitrary argv (including shell -c text) is never emitted.
829
1004
  `(while :; do { mem=$(if [ -r /sys/fs/cgroup/memory.current ]; then cat /sys/fs/cgroup/memory.current; elif [ -r /sys/fs/cgroup/memory/memory.usage_in_bytes ]; then cat /sys/fs/cgroup/memory/memory.usage_in_bytes; else printf -- -; fi); top=$(ps -eo rss=,comm=,args= --sort=-rss | head -5 | awk 'function encode(s) { gsub(/%/, "%25", s); gsub(/:/, "%3A", s); gsub(/ /, "%20", s); gsub(/\\t/, "%09", s); return s } { rss=$1; name=$2; sub(/^[[:space:]]*[0-9]+[[:space:]]+[^[:space:]]+[[:space:]]*/, ""); n=split($0, a, /[[:space:]]+/); cmd="<redacted>"; if (name=="sleep" && a[2]=="30") cmd="sleep 30" (n>2 ? " <redacted>" : ""); else if (name=="bun") { cmd="bun <redacted>"; if (a[2]=="test") cmd="bun test <redacted>"; else if (a[2]=="run") cmd="bun run <redacted>"; else if ((a[2]=="x" || a[2]=="exec") && a[3]=="tsc") cmd="bun x tsc <redacted>" } else if (name=="tsc") cmd="tsc <redacted>"; else if (name=="elanous") { cmd="elanous <redacted>"; if (a[2]=="self") cmd="elanous self <redacted>"; else if (a[2]=="harness") cmd="elanous harness <redacted>" } else if (name=="node") cmd="node <redacted>"; if (name=="sleep" && cmd=="<redacted>") cmd="sleep <redacted>"; else if (cmd=="<redacted>" && name!="bash" && name!="sh") name="other"; printf " %s:%s:%s", rss, encode(name), encode(substr(cmd,1,120)) }'); printf "ELANOUS_MEM %s %s%s\\n" "$(date +%s)" "$mem" "$top"; } || true; sleep 15 || break; done) & mem_sampler_pid=$!`,
830
1005
  ...(o.appCredential ? [podGithubWatchdogScript(o.githubStaleSeconds ?? POD_GH_STALE_SECONDS)] : []),
1006
+ // METER-POD-ROLLUP-GAP — 부분 롤업 표본기: 최종 줄까지 못 사는 Pod 도 N 분마다(기본 5 · ELANOUS_USAGE_PARTIAL_MINUTES) PARTIAL 줄을 하나씩 남긴다.
1007
+ `(usage_partial_seq=0; while :; do { usage_partial_seq=$((usage_partial_seq+1)); printf '%s\\n' "$usage_partial_seq" > ${POD_USAGE_PARTIAL_SEQ_FILE} 2>/dev/null || true; [ -f scripts/usage-rollup.ts ] && bun scripts/usage-rollup.ts --since 12h --partial --seq "$usage_partial_seq" || true; } || true; usage_partial_sleep=$(( \${ELANOUS_USAGE_PARTIAL_MINUTES:-5} * 60 )); sleep "$usage_partial_sleep" || break; done) & usage_partial_pid=$!`,
1008
+ // ⭐ MF-d3b5a70f — si_pid 초기화 + trap: 메인 명령을 아래에서 `& si_pid=$!` 로 띄우고 `wait "$si_pid"` 로
1009
+ // 기다려야 trap 이 grace 안에 발화한다(bash 는 foreground 명령 실행 중 trap 을 미룬다). trap 은
1010
+ // podSigtermTrapScript() 가 내놓는다(수신 측 reemitPodUsage 포맷과 계약을 같은 파일 안에서 공유).
1011
+ 'si_pid=""',
1012
+ podSigtermTrapScript(),
831
1013
  // 마지막 줄 JSON 이 «맨 끝»이어야 한다(parseSelfImplementJson) — rollup 은 그 앞에.
832
1014
  // ⛔ `--author-grade` 는 Pod 안 `elanous`(이미지에 깔린 판)가 모를 수 있어 넘기지 않는다 — 10-06 «unknown option» 즉사(#24445 되돌림).
833
1015
  // AUTHOR-LITE2-POD — 등급은 환경변수 `ELANOUS_AUTHOR_GRADE` 로 싣는다(옛 Pod elanous 는 모르는 환경변수를 무시하고 full 로 돈다).
834
1016
  o.authorSentence
835
- ? `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} -- "$(cat /creds/feature)" > /tmp/si.out 2>&1; rc=$?; echo "ELANOUS_AUTHOR_ON_POD finished host=$(hostname) rc=$rc seconds=$(( $(date +%s) - author_t0 ))"; ${POD_RESULT_FLATTEN}`
1017
+ ? `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=$?; echo "ELANOUS_AUTHOR_ON_POD finished host=$(hostname) rc=$rc seconds=$(( $(date +%s) - author_t0 ))"; ${POD_RESULT_FLATTEN}`
836
1018
  : goalPath
837
- ? `elanous harness ask ${goalPath} --json${quotedAsk} > /tmp/si.out 2>&1; rc=$?; ${POD_RESULT_FLATTEN}`
838
- : `export ${OLD_DOOR_STAMP_ENV}=self-implement; elanous self implement "$(cat /creds/feature)" --json ${quoted} > /tmp/si.out 2>&1; rc=$?`,
1019
+ ? `elanous harness ask ${goalPath} --json${quotedAsk}${quotedChildLlm} > /tmp/si.out 2>&1 & si_pid=$!; wait "$si_pid"; rc=$?; ${POD_RESULT_FLATTEN}`
1020
+ : `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=$?`,
839
1021
  'cat /tmp/si.out',
840
1022
  '[ -f scripts/usage-rollup.ts ] && bun scripts/usage-rollup.ts --since 12h || echo "ELANOUS_USAGE_ROLLUP {\"measured\":false,\"reason\":\"no rollup script\"}"',
841
1023
  // A long run's log export can pass the 5MB artifact limit; skipping it whole left no window into why a
@@ -893,7 +1075,9 @@ done
893
1075
  if [ "$found" -eq 0 ]; then echo ELANOUS_RUN_LEDGER_NONE; fi`,
894
1076
  'kill "$mem_sampler_pid" 2>/dev/null || true',
895
1077
  ...(o.appCredential ? ['kill "$gh_watch_pid" 2>/dev/null || true'] : []),
1078
+ '[ -n "${usage_partial_pid:-}" ] && kill "$usage_partial_pid" 2>/dev/null || true',
896
1079
  'wait "$mem_sampler_pid" 2>/dev/null || true',
1080
+ '[ -n "${usage_partial_pid:-}" ] && wait "$usage_partial_pid" 2>/dev/null || true',
897
1081
  'tail -n 1 /tmp/si.out',
898
1082
  podSalvageScript(),
899
1083
  'exit $rc',
@@ -901,7 +1085,7 @@ if [ "$found" -eq 0 ]; then echo ELANOUS_RUN_LEDGER_NONE; fi`,
901
1085
  return {
902
1086
  apiVersion: 'batch/v1',
903
1087
  kind: 'Job',
904
- metadata: { name: o.name, namespace: o.namespace, labels: { 'elanous.substrate': 'pod', 'elanous.job': o.name, ...podRunLabels(o) },
1088
+ metadata: { name: o.name, namespace: o.namespace, labels: { 'elanous.substrate': 'pod', 'elanous.job': o.name, ...podRunLabels(o), ...(o.workIdentity ? podWorkLabels(o.workIdentity) : {}), ...(o.zai ? { [ZAI_POD_EGRESS_LABEL]: 'true' } : {}) },
905
1089
  ...((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' } : {}) } } : {}) },
906
1090
  spec: {
907
1091
  backoffLimit: 0,
@@ -909,7 +1093,7 @@ if [ "$found" -eq 0 ]; then echo ELANOUS_RUN_LEDGER_NONE; fi`,
909
1093
  activeDeadlineSeconds: o.deadlineSeconds,
910
1094
  template: {
911
1095
  // ⭐ Pod 라벨 — local 팔만 `elanous.egress/local-llm` 을 단다. docker/h1/policy-local-llm.yaml 이 그 라벨에만 호스트 LLM 포트 «하나»를 연다.
912
- metadata: { labels: { 'elanous.job': o.name, ...podRunLabels(o), ...(o.armEnv?.ELANOUS_LLM_PROVIDER === 'local' ? { 'elanous.egress/local-llm': 'true' } : {}) } },
1096
+ 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 ? { [ZAI_POD_EGRESS_LABEL]: 'true' } : {}) } },
913
1097
  spec: {
914
1098
  restartPolicy: 'Never',
915
1099
  securityContext: { runAsUser: 1000, fsGroup: 1000 },
@@ -936,6 +1120,8 @@ if [ "$found" -eq 0 ]; then echo ELANOUS_RUN_LEDGER_NONE; fi`,
936
1120
  { name: ACTUAL_SUBSTRATE_ENV, value: 'pod' }, { name: RUN_CONTRACT_ENV, value: carryRunContract({ substrate: 'pod' }) },
937
1121
  ...Object.entries(o.armEnv ?? {}).map(([name, value]) => ({ name, value })),
938
1122
  ...o.passEnv.map((key) => ({ name: key, valueFrom: { secretKeyRef: { name: `${o.name}-creds`, key: `env-${key}` } } })),
1123
+ ...(o.bedrock ? bedrockPodEnv(o.bedrock) : []),
1124
+ ...(o.zai ? zaiPodEnv(o.zai) : []),
939
1125
  ],
940
1126
  volumeMounts: [{ name: 'creds', mountPath: '/creds', readOnly: true }, ...(o.hostMirror ? [{ name: 'host-mirror', mountPath: '/host-mirror', readOnly: true }] : [])],
941
1127
  }],
@@ -1206,12 +1392,46 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1206
1392
  let ghLoginRetried = false;
1207
1393
  for (;;) {
1208
1394
  try {
1395
+ // ZAI-POD-CHILD — glm(ZAI) 자식은 apply «전»에 동시 상한을 본다: 풀 멤버들의 끝나지 않은 ZAI Job ⊕ 이 프로세스가 정했지만 아직 apply 안 한 수.
1396
+ // 상한 안 → ZAI(이름 있는 Secret · 없으면 거부) · 넘침 → 정책대로(openrouter·balance = 기존 OpenRouter GLM · stop = 거부 · apply 0).
1397
+ let zaiRoute: 'zai' | 'openrouter' | undefined;
1398
+ let zaiSpec: ZaiPodSpec | undefined;
1399
+ zaiPendingApply.delete(name);
1400
+ if (options.provider === 'glm') {
1401
+ const settings = (options.zaiSettings ?? (() => zaiSettingsFromConfig(getUserConfig().llm)))();
1402
+ const poolMembers = (options.pool as { members?: readonly PodPoolMember[] } | undefined)?.members;
1403
+ const contexts = Array.isArray(poolMembers) && poolMembers.length > 0 ? poolMembers.map((m) => m.context) : [context];
1404
+ let active: number | null = 0;
1405
+ for (const c of contexts) {
1406
+ const n = c === context ? countActiveZaiJobs(kubectl, namespace) : countActiveZaiJobs((a, i) => baseKubectl(['--context', c, ...a], i), namespace);
1407
+ if (n === null) { active = null; break; }
1408
+ active += n;
1409
+ }
1410
+ const pending = zaiPendingApply.size;
1411
+ const slot = decideZaiPodSlot({ active, pending, maxConcurrent: settings.maxConcurrent, overage: settings.overage });
1412
+ debug.log('self-implement.pod', 'zai-pod-slot', { job: name, route: slot.route, active: slot.active, pending, maxConcurrent: slot.maxConcurrent, overage: settings.overage, contexts: contexts.length, ...(slot.route !== 'zai' ? { reason: slot.route === 'refuse' ? 'refused' : slot.reason } : {}) }, { level: slot.route === 'zai' ? 'info' : 'warn' });
1413
+ if (slot.route === 'refuse') throw new Error(slot.reason);
1414
+ zaiRoute = slot.route;
1415
+ if (zaiRoute === 'zai') {
1416
+ const checked = checkZaiPodSecret(kubectl, namespace);
1417
+ if (!checked.ok) {
1418
+ debug.log('self-implement.pod', 'zai-secret-refused', { job: name, namespace, reason: checked.reason }, { level: 'warn' });
1419
+ throw new Error(checked.reason);
1420
+ }
1421
+ zaiSpec = checked.spec;
1422
+ zaiPendingApply.add(name);
1423
+ }
1424
+ }
1425
+ /** 넘친 glm Pod 는 자식 인자도 OpenRouter GLM 으로 — Job 스크립트·리뷰어 고정·키 전달이 openrouter 갈래를 그대로 탄다. */
1426
+ const jobArgs = zaiRoute === 'openrouter' ? zaiOverflowChildArgs(args, ZAI_OPENROUTER_FALLBACK_MODEL) : args;
1209
1427
  const grok = options.provider === 'grok';
1210
- const openrouter = options.provider === 'openrouter';
1428
+ const openrouter = options.provider === 'openrouter' || zaiRoute === 'openrouter';
1211
1429
  const anthropic = options.provider === 'anthropic';
1212
- /** API 키 자식 — codex 계정·자격 파일을 안 쓴다(키는 작업별 creds Secret 의 `env-<KEY>` 로만). */
1213
- const apiKeyChild = openrouter || anthropic;
1214
- const plannedAccount = retryAccount ?? (grok ? 'grok' : openrouter ? 'openrouter' : anthropic ? 'anthropic' : options.accountBroker?.() ?? options.account ?? 'team');
1430
+ const bedrockChild = options.provider === 'bedrock';
1431
+ const zaiChild = zaiRoute === 'zai';
1432
+ /** API 키 자식 — codex 계정·자격 파일을 안 쓴다(키는 작업별 creds Secret 의 `env-<KEY>` · bedrock·glm 은 이름 있는 Secret 으로만). */
1433
+ const apiKeyChild = openrouter || anthropic || bedrockChild || zaiChild;
1434
+ const plannedAccount = retryAccount ?? (grok ? 'grok' : openrouter ? 'openrouter' : anthropic ? 'anthropic' : bedrockChild ? 'bedrock' : zaiChild ? 'glm' : options.accountBroker?.() ?? options.account ?? 'team');
1215
1435
  debug.log('self-implement.pod', 'account', { spaceId: input.spaceId, account: plannedAccount, brokered: Boolean(options.accountBroker) });
1216
1436
  const plannedCodexAccounts = grok || apiKeyChild || !options.accountBroker || !options.rotationAccounts ? undefined : [plannedAccount, ...options.rotationAccounts.filter((candidate) => candidate !== plannedAccount && candidate !== failedAccount)];
1217
1437
  if (plannedCodexAccounts && !options.rotationAccounts!.includes(plannedAccount)) throw new Error(`pod: 배분 계정 ${plannedAccount} 이 회전 계획에 없다`);
@@ -1262,6 +1482,19 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1262
1482
  : (await import('../../config.js')).getAnthropicApiKey))()?.trim() || undefined
1263
1483
  : undefined;
1264
1484
  if (anthropic && !anthropicKey) throw new Error('pod: Pod 에 anthropic 키 없음 — 호스트 키 캐시 또는 ANTHROPIC_API_KEY 필요');
1485
+ // BEDROCK-PROVIDER ② — 이름 있는 AWS Secret 의 «키 이름»만 본다(값은 kubectl 밖으로 안 나온다). 없거나 모자라면 apply 0 으로 거부(fail-closed).
1486
+ let bedrockSpec: BedrockPodSpec | undefined;
1487
+ if (bedrockChild) {
1488
+ const hostRegion = (options.bedrockRegion ?? (options.kubectl
1489
+ ? () => validBedrockRegion(env.AWS_REGION?.trim() || env.AWS_DEFAULT_REGION?.trim())
1490
+ : () => resolveBedrockRegion({ env })))();
1491
+ const checked = checkBedrockPodSecret(kubectl, namespace, hostRegion);
1492
+ if (!checked.ok) {
1493
+ debug.log('self-implement.pod', 'bedrock-secret-refused', { job: name, namespace, reason: checked.reason }, { level: 'warn' });
1494
+ throw new Error(checked.reason);
1495
+ }
1496
+ bedrockSpec = checked.spec;
1497
+ }
1265
1498
  const creds = grok
1266
1499
  ? (options.grokCredentials ?? (() => hostGrokCredentials({ env, apiKeyOptIn: options.grokApiKeyOptIn })))()
1267
1500
  : apiKeyChild ? { ghToken: (options.openrouterGhToken ?? defaultGhToken)() }
@@ -1435,7 +1668,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1435
1668
  apiVersion: 'v1', kind: 'Secret', type: 'Opaque',
1436
1669
  metadata: { name: `${name}-creds`, namespace, labels: { 'elanous.job': name } },
1437
1670
  stringData: {
1438
- ...(anthropic ? {}
1671
+ ...(anthropic || bedrockChild || zaiChild ? {}
1439
1672
  : openrouter ? (reviewerCredential ? { 'elanous-auth.json': reviewerCredential.elanousAuth, 'codex-auth.json': reviewerCredential.codexAuth } : {})
1440
1673
  : grok
1441
1674
  ? ('grokAuth' in creds && creds.grokAuth
@@ -1560,11 +1793,20 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1560
1793
  ...(authorChoice.modelSource !== 'default' ? { ELANOUS_PROMPT_ENHANCE_MODEL: authorChoice.model } : {}),
1561
1794
  ...(authorChoice.effortSource !== 'default' ? { ELANOUS_PROMPT_ENHANCE_EFFORT: authorChoice.effort } : {}),
1562
1795
  } : {};
1563
- const jobArmEnv = grounding || credentialRelay || githubRelay || env.ELANOUS_DISPATCH_RECORDED === '1' || options.armEnv || detailUntil || seat || env.ELANOUS_POD_AUTHOR_ON_POD === '1' || reviewerPin
1796
+ const budgetRead = hostBudgetSnapshot(podDelegatedChildLlm(jobArgs) ?? {}, options, env, name);
1797
+ const budgetSnapshot = budgetRead instanceof Promise ? await budgetRead : budgetRead;
1798
+ // REVIEW-ACP-POD ②-b — 호스트가 병합 전에 Claude ACP 로 리뷰할 런이면 Pod 안 api 리뷰를 «호스트로 미룬다»(리뷰 둘 = 돈 두 번 방지).
1799
+ // ⛔ 자동 병합(merge-by-host) 런만 — 호스트 리뷰 문은 merge-ready 에서만 열린다. kubectl 주입(=시험)이면 사용자 설정을 읽지 않는다.
1800
+ const reviewDeferredToHost = input.autoMerge === true
1801
+ && resolvePodHostAcpReview(options.hostAcpReview, env, options.kubectl ? () => undefined : getUserConfig).enabled;
1802
+ if (reviewDeferredToHost) debug.log('self-implement.pod', 'review-deferred-armed', { job: name, env: REVIEW_DEFERRED_TO_HOST_ENV, ...(reviewerPin ? { reviewerPinUnused: reviewerPin } : {}) });
1803
+ const jobArmEnv = grounding || credentialRelay || githubRelay || env.ELANOUS_DISPATCH_RECORDED === '1' || options.armEnv || detailUntil || seat || env.ELANOUS_POD_AUTHOR_ON_POD === '1' || reviewerPin || reviewDeferredToHost || budgetSnapshot
1564
1804
  ? {
1565
1805
  ...armEnv,
1806
+ ...(budgetSnapshot ? { [BUDGET_SNAPSHOT_ENV]: encodeBudgetSnapshot(budgetSnapshot) } : {}),
1566
1807
  ...authorEnv,
1567
1808
  ...(reviewerPin ? { [roleLlmEnvName('review')]: reviewerPin } : {}),
1809
+ ...(reviewDeferredToHost ? { [REVIEW_DEFERRED_TO_HOST_ENV]: '1' } : {}),
1568
1810
  ...(detailUntil ? { ELANOUS_LIVE_DETAIL_UNTIL: detailUntil } : {}),
1569
1811
  ...(env.ELANOUS_DISPATCH_RECORDED === '1' ? { ELANOUS_DISPATCH_RECORDED: '1' } : {}),
1570
1812
  ...(seat === 'OP' || seat === 'TC' || seat === 'MK' || seat === 'UX' ? { ELANOUS_HARNESS_SEAT: seat } : {}),
@@ -1587,10 +1829,24 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1587
1829
  debug.log('self-implement.pod', 'memory-request', { spaceId: input.spaceId, job: name,
1588
1830
  tier: oomRetried ? retryTier : memoryTier, reason: oomRetried ? 'OOMKilled' : memoryReason,
1589
1831
  memoryLimit, memoryRequest });
1590
- const job = podJobManifest({ name, namespace, image: jobImage, ...(member?.imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}), repoUrl, ...(options.source ? { source: options.source } : {}), ...(hostMirror ? { hostMirror } : {}), args, passEnv: jobPassEnv, deadlineSeconds: options.deadlineSeconds ?? POD_JOB_DEADLINE_SECONDS, ...(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: options.imageCommit !== undefined ? options.imageCommit : options.kubectl ? null : podImageFreshness({ image }).imageCommit, ...(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, ...(reviewerCredential ? { reviewerCodexAuth: true } : {}) }); // kubectl 주입(=시험)이면 docker 를 부르지 않는다
1832
+ const ownerKind = env.ELANOUS_POD_OWNER_KIND;
1833
+ const workIdentity: PodWorkIdentity = {
1834
+ ...(ownerKind === 'seat' || ownerKind === 'steward' || ownerKind === 'orchestrator' || ownerKind === 'external'
1835
+ ? { ownerKind, ownerId: env.ELANOUS_POD_OWNER_ID } : seat === 'OP' || seat === 'TC' || seat === 'MK' || seat === 'UX'
1836
+ ? { ownerKind: 'seat', ownerId: seat } : {}),
1837
+ taskCard: env.ELANOUS_POD_TASK_CARD ?? env.ELANOUS_TASK_CARD_ID,
1838
+ slotId: env.ELANOUS_POD_SLOT_ID,
1839
+ work: !goalDoc && env.ELANOUS_POD_AUTHOR_ON_POD === '1' && !shard ? 'authoring' : (oomRetried ? retryTier : memoryTier) === 'lite' ? 'lite' : 'harness',
1840
+ };
1841
+ const job = podJobManifest({ name, namespace, image: jobImage, workIdentity, ...(member?.imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}), repoUrl, ...(options.source ? { source: options.source } : {}), ...(hostMirror ? { hostMirror } : {}), args: jobArgs, passEnv: jobPassEnv, deadlineSeconds: options.deadlineSeconds ?? POD_JOB_DEADLINE_SECONDS, ...(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: options.imageCommit !== undefined ? options.imageCommit : options.kubectl ? null : podImageFreshness({ image }).imageCommit, ...(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, ...(reviewerCredential ? { reviewerCodexAuth: true } : {}), ...(bedrockSpec ? { bedrock: bedrockSpec } : {}), ...(zaiSpec ? { zai: zaiSpec } : {}) }); // kubectl 주입(=시험)이면 docker 를 부르지 않는다
1842
+ const carriedBranch = goalDoc ? 'goal-doc' as const : env.ELANOUS_POD_AUTHOR_ON_POD === '1' && !shard ? 'author' as const : undefined;
1843
+ const carriedChildLlm = carriedBranch ? podDelegatedChildLlm(jobArgs) : undefined;
1591
1844
  const a = kubectl(['apply', '-f', '-'], JSON.stringify(job));
1845
+ zaiPendingApply.delete(name); // apply 뒤엔 Job 라벨이 센다(성공) · 실패면 셀 것이 없다
1592
1846
  if (a.status !== 0) { cleanupSecret(); return { exitCode: 1, output: a.stderr, error: { code: 'pod-apply', message: a.stderr.trim() } }; }
1593
1847
  releaseAdmission?.applied?.(name, context, namespace);
1848
+ // Logged only once the Job is applied — a failed apply carried nothing.
1849
+ if (carriedBranch && carriedChildLlm) debug.log('self-implement.pod', 'child-llm-carried', { spaceId: input.spaceId, job: name, branch: carriedBranch, provider: carriedChildLlm.provider, model: carriedChildLlm.model ?? null });
1594
1850
  if (options.source?.kind === 'bundle') {
1595
1851
  const podName = await waitForRunningPod(kubectl, namespace, name, sleep);
1596
1852
  if (!podName) {
@@ -1634,15 +1890,26 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1634
1890
  }
1635
1891
  writePodFragment({ spaceId: input.spaceId, context, namespace, job: name, inboxDir: POD_CONTROL_INBOX_DIR, ...(liveChildRunId === normalizeRunId(liveChildRunId) ? { runId: liveChildRunId } : {}), ...(parentRunId && normalizeRunId(parentRunId) === parentRunId ? { parentRunId } : {}) }, env);
1636
1892
  recorded = true;
1637
- 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') } : {}), ...(anthropic ? { anthropicKeyPresent: passEnv.includes('ANTHROPIC_API_KEY') } : {}), extraArgs: options.extraArgs ?? [], ...(options.armEnv?.ELANOUS_ARM_ID ? { armId: options.armEnv.ELANOUS_ARM_ID } : {}) });
1893
+ 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') } : {}), ...(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 } : {}) });
1638
1894
  let state: 'complete' | 'failed' | 'aborted' = 'failed';
1639
1895
  let failedReason = '';
1640
1896
  let containerReason: string | null = null;
1641
1897
  let containerExitCode: number | null = null;
1898
+ // METER-POD-ROLLUP-GAP — abort 때 Job 삭제 «전»에 거둔 로그 사본(삭제 뒤엔 PARTIAL 줄도 못 읽는다).
1899
+ let abortLogs = '';
1642
1900
  // ⭐ 런 «도중» 원장 증분 회수 — 호스트 슈퍼바이저가 Pod 걸음을 실시간으로 본다(🅣 요청 · 힐 루프 runtime 성형의 전제).
1643
1901
  const follower = createPodLedgerFollower({ runId: liveChildRunId, dir: ledgerDir, exec: (script) => kubectl(['-n', namespace, 'exec', `job/${name}`, '-c', 'child', '--', 'sh', '-c', script]) });
1902
+ // ⭐ METER-POD-ROLLUP-GAP-3 — 런 «도중» 마지막 PARTIAL 줄을 호스트가 쥔다: Pod 가 지워지면(SIGTERM 등) 종료 로그에서 PARTIAL 이 사라진다.
1903
+ let livePartialLine: string | null = null;
1904
+ let pollsSinceCapture = 0;
1905
+ const capturePolls = Math.max(1, options.usagePartialCapturePolls ?? Math.round(POD_USAGE_PARTIAL_CAPTURE_MS / Math.max(1, options.pollMs ?? 15_000)));
1644
1906
  for (;;) {
1645
1907
  if (input.signal?.aborted) {
1908
+ // METER-POD-ROLLUP-GAP — Job 삭제 «전»에 로그를 한 번 거둔다: PARTIAL 줄이 삭제와 함께 사라지면 사용량이 0 처럼 보인다.
1909
+ try {
1910
+ const captured = kubectl(['-n', namespace, 'logs', `job/${name}`, '-c', 'child', '--tail=400']);
1911
+ if (captured.status === 0 && captured.stdout) abortLogs = captured.stdout;
1912
+ } catch { /* 수거 실패가 abort 자체를 막아선 안 된다 */ }
1646
1913
  kubectl(['-n', namespace, 'delete', 'job', name, '--ignore-not-found', '--wait=false']);
1647
1914
  state = 'aborted';
1648
1915
  break;
@@ -1695,6 +1962,18 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1695
1962
  }
1696
1963
  }
1697
1964
  try { follower?.poll(); } catch (e) { debug.log('self-implement.pod', 'ledger-live-unavailable', { job: name, reason: e instanceof Error ? e.message : String(e) }); }
1965
+ if (++pollsSinceCapture >= capturePolls) {
1966
+ pollsSinceCapture = 0;
1967
+ try {
1968
+ const sampled = kubectl(['-n', namespace, 'logs', `job/${name}`, '-c', 'child', `--since=${POD_USAGE_PARTIAL_CAPTURE_SINCE}`]);
1969
+ const seen = sampled.status === 0 ? lastPodUsagePartialLine(sampled.stdout) : null;
1970
+ // 창 안에 새 줄이 없으면 앞서 쥔 줄을 지킨다(덮어 지우지 않는다).
1971
+ if (seen) {
1972
+ livePartialLine = seen;
1973
+ debug.log('self-implement.pod', 'usage-partial-captured', { job: name, bytes: seen.length });
1974
+ }
1975
+ } catch (e) { debug.log('self-implement.pod', 'usage-partial-capture-failed', { job: name, reason: e instanceof Error ? e.message : String(e) }); }
1976
+ }
1698
1977
  await sleep(options.pollMs ?? 15_000);
1699
1978
  }
1700
1979
  let logs = '';
@@ -1705,6 +1984,8 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1705
1984
  else if (!fetched.stdout) logTailReason = 'empty child logs';
1706
1985
  else logs = fetched.stdout;
1707
1986
  } catch (error) { logTailReason = error instanceof Error ? error.message : String(error); }
1987
+ // METER-POD-ROLLUP-GAP — 삭제된 abort Job 의 로그는 이후 못 읽는다: abort 때 거둔 사본으로 대신한다.
1988
+ if (!logs && abortLogs) logs = abortLogs;
1708
1989
  let ledgerReason = '';
1709
1990
  let ledgerCollected = false;
1710
1991
  const collection = collectFullLogs('ledger-collect-incomplete', follower.owned ? new Set([liveChildRunId]) : undefined, (event, data) => {
@@ -1731,7 +2012,10 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1731
2012
  cleanupSecret();
1732
2013
  // ⭐ 호스트 단가로 다시 매긴다(BACKLOG C1b) — Pod 엔 레지스트리 스냅숏이 없다.
1733
2014
  const { estimateLlmCost } = await import('../../budget/llm-cost.js');
1734
- reemitPodUsage(logs, name, undefined, (u) => estimateLlmCost(u) as { kind: string; usd?: number });
2015
+ // METER-POD-ROLLUP-GAP — 최종 줄 → 마지막 PARTIAL 폴백 → 결손 행(measured:false · 종료 사유): 사용량이 0 처럼 보이는 침묵을 남기지 않는다.
2016
+ // METER-POD-ROLLUP-GAP-3 — 판정은 kubectl 없는 순수 함수(podUsageReemitPlan)가 하고 여기선 행을 내보내기만 한다(ux-sim 심이 같은 함수를 부른다).
2017
+ const usagePlan = podUsageReemitPlan({ job: name, runId: liveChildRunId, logs, livePartialLine, exit: { state, failedReason, containerReason, containerExitCode } }, (u) => estimateLlmCost(u) as { kind: string; usd?: number });
2018
+ for (const row of usagePlan.rows) debug.log(row.category, row.event, row.data);
1735
2019
  const parsed = parseSelfImplementJson(logs);
1736
2020
  const terminal = parseSelfImplementJson(logs.trimEnd().split('\n').at(-1) ?? '');
1737
2021
  const nextAccount = codexAccounts?.find((candidate) => candidate !== account);
@@ -1776,7 +2060,38 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1776
2060
  if (!taLandOwner) debug.log('self-implement.pod', 'ta-land-marker-ignored', { job: name, pr: prNumber ?? null,
1777
2061
  reason: unreadable !== null ? 'live-moves-unreadable' : 'propose-land-not-enabled', ...(unreadable !== null ? { error: unreadable } : {}) });
1778
2062
  }
2063
+ // REVIEW-ACP-POD — opt-in: 재게이트 «전»에 호스트 Claude ACP(구독) 리뷰. must-fix 면 병합하지 않고 PR 을 연 채 둔다.
2064
+ // ⛔ 못 쓰면(refused) 종전 길 그대로 — Pod 리뷰 결과로 재게이트한다(다른 공급자로 새지 않는다 · 관측 `acp-review-refused`).
2065
+ // ⛔ kubectl 주입(=시험)이면 사용자 설정을 읽지 않는다.
2066
+ let hostAcpMustFix: Extract<PodHostAcpReviewOutcome, { status: 'must-fix' }> | undefined;
2067
+ // REVIEW-ACP-POD ②-b — Pod 가 리뷰를 «미뤘다»면(mergeReason) 호스트 ACP 리뷰가 «유일한» 리뷰다:
2068
+ // 스위치가 그 사이 꺼졌어도 리뷰한다 · 못 하면(refused) 병합하지 않는다(fail-closed — 리뷰 없이 병합 0).
2069
+ const podReviewDeferred = disposition.mergeReason === REVIEW_DEFERRED_TO_HOST_MERGE_REASON;
2070
+ let hostAcpUnavailable: string | undefined;
2071
+ const hostAcpSwitch = resolvePodHostAcpReview(options.hostAcpReview, env, options.kubectl ? () => undefined : getUserConfig);
2072
+ if ((hostAcpSwitch.enabled || podReviewDeferred) && Number.isSafeInteger(prNumber) && prNumber && headCommit && /^[0-9a-f]{40}$/i.test(headCommit)) {
2073
+ const hostRoot = findGitDir(process.cwd())?.root;
2074
+ const outcome: PodHostAcpReviewOutcome = hostRoot
2075
+ ? await (options.podHostAcpReview ?? ((i) => runPodHostAcpReview(i, { env })))({ prNumber, headCommit, repoRoot: hostRoot, job: name })
2076
+ : { status: 'refused', reason: 'host repository unavailable' };
2077
+ if (outcome.status === 'refused' && !hostRoot) debug.log('self-implement.pod', 'acp-review-refused', { job: name, pr: prNumber, reason: outcome.reason }, { level: 'warn' });
2078
+ debug.log('self-implement.pod', 'host-acp-review-gate', { job: name, pr: prNumber, source: hostAcpSwitch.enabled ? hostAcpSwitch.source : 'pod-deferred', podReviewDeferred, status: outcome.status, ...(outcome.status === 'refused' ? { reason: outcome.reason, path: podReviewDeferred ? 'hold-pr' : 'pod-review' } : { mustFix: outcome.mustFix }) });
2079
+ if (outcome.status === 'must-fix') hostAcpMustFix = outcome;
2080
+ if (outcome.status === 'refused' && podReviewDeferred) hostAcpUnavailable = outcome.reason;
2081
+ }
1779
2082
  let regate: HostRegateResult;
2083
+ if (hostAcpUnavailable !== undefined) {
2084
+ // ⛔ Pod 안 리뷰가 없다 — 재게이트·병합으로 가면 «리뷰 없이 병합»이다. PR 을 연 채 둔다.
2085
+ debug.log('self-implement.pod', 'host-acp-review-unavailable', { job: name, pr: prNumber ?? null, head: headCommit ?? null, reason: hostAcpUnavailable }, { level: 'warn' });
2086
+ try { (options.ghComment ?? defaultGhComment)(prNumber!, `호스트 Claude ACP 리뷰를 못 했다(${hostAcpUnavailable.replace(/\s+/g, ' ').slice(0, 200)}) — Pod 안 리뷰는 호스트로 미뤄져 없으므로 자동 병합하지 않는다(REVIEW-ACP-POD ②-b).`); }
2087
+ catch (error) { debug.log('self-implement.pod', 'host-acp-review-comment-failed', { job: name, pr: prNumber ?? null, detail: (error instanceof Error ? error.message : String(error)).slice(0, 200) }); }
2088
+ disposition = { ...disposition, stage: 'pr-opened', merged: false, ok: false, mergeReason: 'host-acp-review-unavailable' };
2089
+ } else if (hostAcpMustFix) {
2090
+ const body = [`호스트 Claude ACP 리뷰: must-fix ${hostAcpMustFix.mustFix}건 — 자동 병합하지 않는다(REVIEW-ACP-POD).`, ...hostAcpMustFix.findings.map((f, i) => `${i + 1}. ${f.replace(/\s+/g, ' ').slice(0, 400)}`)].join('\n');
2091
+ try { (options.ghComment ?? defaultGhComment)(prNumber!, body); }
2092
+ catch (error) { debug.log('self-implement.pod', 'host-acp-review-comment-failed', { job: name, pr: prNumber ?? null, detail: (error instanceof Error ? error.message : String(error)).slice(0, 200) }); }
2093
+ disposition = { ...disposition, stage: 'pr-opened', merged: false, ok: false, mergeReason: 'host-acp-review-must-fix' };
2094
+ } else {
1780
2095
  if (!Number.isSafeInteger(prNumber) || !prNumber || !headCommit || !/^[0-9a-f]{40}$/i.test(headCommit)) {
1781
2096
  debug.log('harness.host-regate', 'unmeasured', { pr: prNumber ?? null, files: [], os: process.platform, failedStep: 'pod-result' });
1782
2097
  regate = { passed: false, failures: [{ step: 'pod-result', detail: 'missing PR number or checked head commit' }], os: process.platform };
@@ -1809,6 +2124,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1809
2124
  if (taLandOwner) debug.log('self-implement.pod', 'ta-land-owner', { job: name, pr: prNumber ?? null, head: headCommit ?? null, regatePassed: false });
1810
2125
  disposition = { ...disposition, stage: frozen || releaseHold ? 'pr-opened' : regate.passed ? 'merged' : 'host-regate-failed', merged: regate.passed && !frozen, hostRegate: regate, ok: regate.passed || releaseHold || frozen };
1811
2126
  }
2127
+ }
1812
2128
  }
1813
2129
  const childFailure = state === 'failed' && !oomKilled && failedReason !== 'DeadlineExceeded'
1814
2130
  ? lastPodChildFailure(logs) : null;
@@ -1928,6 +2244,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1928
2244
  ...(finishedDisposition ? { disposition: finishedDisposition } : {}),
1929
2245
  };
1930
2246
  } catch (err) {
2247
+ zaiPendingApply.delete(name);
1931
2248
  cleanupSecret();
1932
2249
  const message = err instanceof Error ? err.message : String(err);
1933
2250
  debug.log('self-implement.pod', 'job-error', { job: name, message }, { level: 'error' });
@@ -2046,11 +2363,49 @@ function formatLastMemSample(sample: ReturnType<typeof parseMemSamples>[number]
2046
2363
  return `마지막 샘플: cgroup ${sample.cgroupBytes === null ? '-' : gib(sample.cgroupBytes)} · 상위 ${sample.top.length ? sample.top.map((p) => `${p.name} ${gib(p.rssKb * 1024)}`).join(' · ') : '없음'}`;
2047
2364
  }
2048
2365
 
2049
- export function reemitPodUsage(logs: string, job: string, log: (category: string, event: string, data: Record<string, unknown>) => void = (c, e, d) => debug.log(c, e, d), reprice?: PodRowReprice): number {
2050
- const line = logs.split('\n').reverse().find((l) => l.startsWith('ELANOUS_USAGE_ROLLUP '));
2051
- if (!line) { log('self-implement.pod', 'usage-rollup-missing', { job }); return 0; }
2052
- let parsed: { measured?: boolean; truncated?: boolean; runId?: string | null; armId?: string | null; podName?: string | null; nodeName?: string | null; hostId?: string | null; rows?: Array<Record<string, unknown>> };
2053
- try { parsed = JSON.parse(line.slice('ELANOUS_USAGE_ROLLUP '.length)); } catch { log('self-implement.pod', 'usage-rollup-unparsable', { job }); return 0; }
2366
+ /** METER-POD-ROLLUP-GAP — 결손 칸의 종료 사유 토큰: 최종도 부분도 없는 사용량을 0 이 아니 «측정 불가» 로 남긴다. */
2367
+ export type PodUsageMissingReason = 'stopped' | 'sigterm-143' | 'backoff-limit' | 'deadline' | 'oom' | 'logs-unavailable';
2368
+
2369
+ /** 종료 사유 분류 — 로그를 못 읽으면 그것부터, 그다음 SIGTERM 143 → Backoff → Deadline → OOM, 나머지는 stopped. */
2370
+ export function podUsageMissingReason(input: { logsAvailable: boolean; exitCode?: number | null; failedReason?: string }): PodUsageMissingReason {
2371
+ if (!input.logsAvailable) return 'logs-unavailable';
2372
+ if (input.exitCode === 143) return 'sigterm-143';
2373
+ if (input.failedReason === 'BackoffLimitExceeded') return 'backoff-limit';
2374
+ if (input.failedReason === 'DeadlineExceeded') return 'deadline';
2375
+ if (input.failedReason === 'OOMKilled' || input.exitCode === 137) return 'oom';
2376
+ return 'stopped';
2377
+ }
2378
+
2379
+ const POD_USAGE_PARTIAL_PREFIX = 'ELANOUS_USAGE_ROLLUP_PARTIAL ';
2380
+
2381
+ /** METER-POD-ROLLUP-GAP-3 — 로그 덩어리에서 «마지막» PARTIAL 줄(없으면 null). 런 도중 표본과 종료 로그가 같은 판정을 쓴다. */
2382
+ export function lastPodUsagePartialLine(logs: string): string | null {
2383
+ return logs.split('\n').reverse().find((l) => l.startsWith(POD_USAGE_PARTIAL_PREFIX)) ?? null;
2384
+ }
2385
+
2386
+ /** METER-POD-ROLLUP-GAP-3 — 호스트가 런 «도중» 거두는 PARTIAL 표본 간격(Pod 표본기 기본 5분과 맞춘다) ⊕ 그때 읽는 로그 창(표본 간격 둘 ⊕ 여유). */
2387
+ export const POD_USAGE_PARTIAL_CAPTURE_MS = 5 * 60_000;
2388
+ export const POD_USAGE_PARTIAL_CAPTURE_SINCE = '11m';
2389
+
2390
+ export function reemitPodUsage(logs: string, job: string, log: (category: string, event: string, data: Record<string, unknown>) => void = (c, e, d) => debug.log(c, e, d), reprice?: PodRowReprice, missing?: { reason: PodUsageMissingReason; runId?: string | null; podExitClass?: PodExitClass | null; livePartialLine?: string | null }): number {
2391
+ const lines = logs.split('\n').reverse();
2392
+ const line = lines.find((l) => l.startsWith('ELANOUS_USAGE_ROLLUP '));
2393
+ // ⭐ METER-POD-ROLLUP-GAP — 최종 줄이 없으면 마지막 PARTIAL 줄로 폴백한다(부분 롤업 표본기가 남긴 줄).
2394
+ // ⭐ METER-POD-ROLLUP-GAP-3 — 종료 로그에도 PARTIAL 이 없으면(Pod 가 지워져 로그가 사라졌다) 런 «도중» 호스트가 거둔 마지막 PARTIAL 로.
2395
+ const logPartial = line ? null : lastPodUsagePartialLine(logs);
2396
+ const livePartial = line || logPartial || !missing?.livePartialLine?.startsWith(POD_USAGE_PARTIAL_PREFIX) ? null : missing.livePartialLine;
2397
+ const partialLine = logPartial ?? livePartial;
2398
+ if (!line && !partialLine) {
2399
+ // ⭐ METER-POD-ROLLUP-GAP-3 — 결손 이벤트에도 종료 사유를 싣는다(옛 줄은 {job} 뿐이라 «왜 없나» 를 못 물었다).
2400
+ const why = missing ? { reason: missing.reason, ...(missing.podExitClass ? { podExitClass: missing.podExitClass } : {}), ...(missing.runId ? { runId: missing.runId } : {}) } : {};
2401
+ log('self-implement.pod', 'usage-rollup-missing', { job, ...why });
2402
+ // ⭐ 결손 행 — «측정 불가» 를 한 줄로 남긴다: 0 처럼 보이지 않게.
2403
+ if (missing) log('llm.usage', 'llm-usage', { site: 'pod-rollup:missing', measured: false, ...why, job, substrate: 'pod', podRollupKey: `${missing.runId ?? job}|missing` });
2404
+ return 0;
2405
+ }
2406
+ const prefix = line ? 'ELANOUS_USAGE_ROLLUP ' : POD_USAGE_PARTIAL_PREFIX;
2407
+ let parsed: { measured?: boolean; truncated?: boolean; runId?: string | null; armId?: string | null; podName?: string | null; nodeName?: string | null; hostId?: string | null; seq?: number; at?: string; rows?: Array<Record<string, unknown>> };
2408
+ try { parsed = JSON.parse((line ?? partialLine)!.slice(prefix.length)); } catch { log('self-implement.pod', 'usage-rollup-unparsable', { job }); return 0; }
2054
2409
  const rows = parsed.rows ?? [];
2055
2410
  for (const r of rows) {
2056
2411
  const cost = podRowCost(r, reprice);
@@ -2060,15 +2415,57 @@ export function reemitPodUsage(logs: string, job: string, log: (category: string
2060
2415
  // ⭐ 캐시 쓰기 — 옛 Pod 이미지 rollup 엔 칸이 없다 ⇒ 없으면 «잰 0» 으로 꾸미지 않고 뺀다.
2061
2416
  ...(r.cacheCreationInputTokens !== undefined ? { cacheCreationInputTokens: r.cacheCreationInputTokens } : {}),
2062
2417
  ...(Number(r.cacheCreationUnmeasuredCalls ?? 0) > 0 ? { cacheCreationUnmeasuredCalls: r.cacheCreationUnmeasuredCalls } : {}),
2418
+ // OR-BYOK-COST — BYOK 출처 칸(비용 자체는 usdKnown/unknownCostCalls 에 이미 있다). 옛 Pod rollup·비-BYOK 행엔 없다 ⇒ 안 싣는다.
2419
+ ...(Number(r.byokCalls ?? 0) > 0 ? { byokCalls: r.byokCalls, ...(typeof r.byokUpstreamUsd === 'number' ? { byokUpstreamUsd: r.byokUpstreamUsd } : {}), ...(Number(r.byokUpstreamUnknownCalls ?? 0) > 0 ? { byokUpstreamUnknownCalls: r.byokUpstreamUnknownCalls } : {}) } : {}),
2063
2420
  cost,
2064
2421
  substrate: 'pod', job, ...(parsed.runId ? { podRunId: parsed.runId } : {}), ...(parsed.armId ? { armId: parsed.armId } : {}),
2422
+ // ⭐ MF-3c0d253e — 재방출 행의 downstream dedup 키: 같은 Pod(job·runId)의 같은 버킷(site·provider·model)은
2423
+ // 부분(seq 달라도)·최종·abort⊕resume 재방출이 몇 번 만나도 집계(rollup)에서 한 번만 센다. 키 없는 기존 행은 종전대로.
2424
+ // ⭐ METER-POD-ROLLUP-GAP-2 — podName 을 키에 넣는다: gh-login·usage-limit 재시도는 «같은 job 이름·runId» 로 새 Pod 를 띄우므로
2425
+ // (podJobName 은 spaceId 결정적) 그것 없이는 서로 다른 시도의 사용량이 한 번으로 접혀 앞 시도분이 사라진다.
2426
+ podRollupKey: `${job}|${parsed.podName ?? ''}|${parsed.runId ?? ''}|pod-rollup:${String(r.site)}|${String(r.provider)}|${String(r.model)}`,
2065
2427
  ...(parsed.podName ? { podName: parsed.podName } : {}), ...(parsed.nodeName ? { nodeName: parsed.nodeName } : {}), ...(parsed.hostId ? { podHostId: parsed.hostId } : {}),
2428
+ // ⭐ PARTIAL 폴백 재방출 꼬리표 — 최종 줄 행은 그대로(부분이 아님을 꾸미지 않는다).
2429
+ ...(partialLine ? { partial: true, ...(parsed.seq !== undefined ? { partialSeq: parsed.seq } : {}), ...(parsed.at !== undefined ? { partialAt: parsed.at } : {}), ...(livePartial ? { partialSource: 'live-capture' } : {}) } : {}),
2066
2430
  });
2067
2431
  }
2068
- log('self-implement.pod', 'usage-rollup', { job, rows: rows.length, measured: parsed.measured ?? null, truncated: parsed.truncated ?? null });
2432
+ log('self-implement.pod', 'usage-rollup', { job, rows: rows.length, measured: parsed.measured ?? null, truncated: parsed.truncated ?? null, ...(partialLine ? { partial: true, ...(parsed.seq !== undefined ? { seq: parsed.seq } : {}), ...(parsed.at !== undefined ? { at: parsed.at } : {}), ...(livePartial ? { partialSource: 'live-capture' } : {}) } : {}) });
2069
2433
  return rows.length;
2070
2434
  }
2071
2435
 
2436
+ /** METER-POD-ROLLUP-GAP-3 — 호스트 사용량 재방출 판정의 «순수» 입력·출력(kubectl·debug 없음). */
2437
+ export interface PodUsageReemitInput {
2438
+ job: string;
2439
+ runId?: string | null;
2440
+ /** Job 이 끝난 뒤 거둔 로그(Pod 가 사라졌으면 ''). */
2441
+ logs: string;
2442
+ /** 런 «도중» 호스트가 본 마지막 `ELANOUS_USAGE_ROLLUP_PARTIAL` 줄(없으면 null). */
2443
+ livePartialLine?: string | null;
2444
+ /** 종료 정보 — state 가 'failed' 일 때만 classifyPodExit 분류를 싣는다. */
2445
+ exit: { state: 'complete' | 'failed' | 'aborted'; failedReason?: string | null; containerReason?: string | null; containerExitCode?: number | null };
2446
+ }
2447
+ export interface PodUsageReemitPlan {
2448
+ /** 어느 줄로 재방출했나 — final(최종 줄) · partial-log(끝 로그의 PARTIAL) · partial-live(런 도중 본 PARTIAL) · missing(결손 행) · unparsable. */
2449
+ source: 'final' | 'partial-log' | 'partial-live' | 'missing' | 'unparsable';
2450
+ /** 그대로 debug.log(category, event, data) 로 내보낼 행들(llm-usage 행 ⊕ self-implement.pod 요약/결손 이벤트). */
2451
+ rows: Array<{ category: string; event: string; data: Record<string, unknown> }>;
2452
+ }
2453
+
2454
+ /** METER-POD-ROLLUP-GAP-3 — 끝 로그 ⊕ 런 도중 PARTIAL ⊕ 종료 정보 → 재방출할 행/결손 행. 호스트 Job 경로와 ux-sim 심이 «같은» 함수를 부른다. */
2455
+ export function podUsageReemitPlan(input: PodUsageReemitInput, reprice?: PodRowReprice): PodUsageReemitPlan {
2456
+ const { job, logs, exit } = input;
2457
+ const reason = podUsageMissingReason({ failedReason: exit.failedReason ?? undefined, exitCode: exit.containerExitCode ?? null, logsAvailable: Boolean(logs) });
2458
+ const podExitClass = exit.state === 'failed' ? classifyPodExit({ containerReason: exit.containerReason ?? null, containerExitCode: exit.containerExitCode ?? null, jobReason: exit.failedReason || null, logs }).class : null;
2459
+ const rows: PodUsageReemitPlan['rows'] = [];
2460
+ reemitPodUsage(logs, job, (category, event, data) => rows.push({ category, event, data }), reprice, { reason, runId: input.runId ?? null, podExitClass, livePartialLine: input.livePartialLine ?? null });
2461
+ const usage = rows.find((r) => r.category === 'llm.usage');
2462
+ const source: PodUsageReemitPlan['source'] = rows.some((r) => r.event === 'usage-rollup-missing') ? 'missing'
2463
+ : rows.some((r) => r.event === 'usage-rollup-unparsable') ? 'unparsable'
2464
+ : rows.find((r) => r.event === 'usage-rollup')?.data.partialSource === 'live-capture' ? 'partial-live'
2465
+ : rows.find((r) => r.event === 'usage-rollup')?.data.partial === true || usage?.data.partial === true ? 'partial-log' : 'final';
2466
+ return { source, rows };
2467
+ }
2468
+
2072
2469
  /** 이 기계가 Pod 칸을 쓸 수 있나(클러스터 · 이미지) — 발사 전 한 번. */
2073
2470
  export function podSubstrateReady(kubectl: Kubectl = defaultKubectl, image = 'elanous-harness:local'): { ok: boolean; reason: string } {
2074
2471
  const ctx = kubectl(['config', 'current-context']);