elanous 0.2.34 → 0.2.35

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 (611) 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/{L04y73dlwKeTW-GetaTF3 → T1mBQqmkwKDLGtyisDCOd}/_buildManifest.js +1 -1
  4. package/apps/pwa/out/_next/static/chunks/{5014f970-22bef0b5210f7d60.js → 0368f25d-ad84af92fefee68f.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/1165.bad13fc9151a7288.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/13.02a07436dec15f89.js +32 -0
  7. package/apps/pwa/out/_next/static/chunks/{2124.ed5bf480cea7f85b.js → 1530.0e612369b6fd7ea0.js} +1 -1
  8. package/apps/pwa/out/_next/static/chunks/1540.af38731fa7c4a3e0.js +41 -0
  9. package/apps/pwa/out/_next/static/chunks/1674.2a65fd5668e1cc5f.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/1850.f2b470ba5a471da0.js +24 -0
  11. package/apps/pwa/out/_next/static/chunks/193.62da94cd0b88323b.js +74 -0
  12. package/apps/pwa/out/_next/static/chunks/1975.260f6d249fac5f7c.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/1982-67cd8dfe8682325f.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/1993-c0007b7ad0c0ef41.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/1999-bb415351c8a2846c.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/1c735de4.f993a30bbd3c57c4.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/1dc5a567.9b092bfae3d7099b.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/{8368.014caa8de2f5af34.js → 2018.a741297bce759490.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/2085.8f950b4bda1cbbc7.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/2159-6179a961376956b9.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/216.b5c2a421aa1d28a5.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/2251.49fbe369f74c372c.js +148 -0
  23. package/apps/pwa/out/_next/static/chunks/2286-2939577360d3dcc0.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/2625.5d870aa999f33db1.js +34 -0
  25. package/apps/pwa/out/_next/static/chunks/2668.34d21539e62bf111.js +77 -0
  26. package/apps/pwa/out/_next/static/chunks/2833-0fe47af99ed13c29.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/2930.d0edb4f108787ef7.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/2973.77318e7d85f59477.js +62 -0
  29. package/apps/pwa/out/_next/static/chunks/3103.0df8da390948bb84.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/3123.2bee9ba15d320bd2.js +90 -0
  31. package/apps/pwa/out/_next/static/chunks/3162-6cf652bf7124d8cf.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/{1644.35dd25240ae24e4b.js → 3408.21d1570602f4b6c0.js} +2 -2
  33. package/apps/pwa/out/_next/static/chunks/3577-7340009f7007a795.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/{3121.779fafe3e44612c5.js → 3589.5444a06bcf6e775c.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/359-2250789a2d3499e7.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/3618.f514792d6c59d7fd.js +36 -0
  37. package/apps/pwa/out/_next/static/chunks/3669.343f9119aa276001.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/{9131-fce7256c4180c4a5.js → 3677-4215f826f6aa68e5.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/3736.c2efad489604cc3b.js +79 -0
  40. package/apps/pwa/out/_next/static/chunks/3817.f9aeab2bb6675d73.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/3f7c4ee5.3dde54f01a816613.js +122 -0
  42. package/apps/pwa/out/_next/static/chunks/4182-0b9b1251c29cc0cf.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/4346.8601952e73bf5803.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/{6053-d893974de92e6198.js → 4441-875b97f5821ed58a.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/4582.bd68683c076649e3.js +286 -0
  46. package/apps/pwa/out/_next/static/chunks/4605-6e623f55aec77422.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/4607.5cc27468628eb4bf.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/4627.f07508b3936fd6c0.js +24 -0
  49. package/apps/pwa/out/_next/static/chunks/{1895-60ed0e341c658bd4.js → 4705-194e64e47ca0fbdb.js} +1 -1
  50. package/apps/pwa/out/_next/static/chunks/{5576-82d3703636d5c4b9.js → 4718-17d2af636796a330.js} +3 -3
  51. package/apps/pwa/out/_next/static/chunks/4747-c22262c093e46807.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/4775-178215aed0daba61.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/4815-6ffb8c3c991cc45d.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/4834-beca8b47ad29a898.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/5023-fb4802b6d2492b02.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/{7431.3051e9b8a4372d2d.js → 5101.e32282718f99ed6f.js} +2 -2
  57. package/apps/pwa/out/_next/static/chunks/5154.a118f3957ed14e36.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/5210-a2e0015e3d4d5d9c.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/5356.84ac712cc15928bf.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/5401.22352141b6fd336e.js +23 -0
  61. package/apps/pwa/out/_next/static/chunks/{2987-30cf9c2eb7b8bdd5.js → 5483-dd5d7697f6bdb106.js} +1 -1
  62. package/apps/pwa/out/_next/static/chunks/5637.281e5dec4ea6ecf1.js +94 -0
  63. package/apps/pwa/out/_next/static/chunks/5662.02eaa029af05ad46.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/56d372f2.681ab4ec4c998fae.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/5760.22456f433c6f42c0.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/{7386.5d8e7e42e818dfb6.js → 5852.db1f7449f4f74612.js} +2 -2
  67. package/apps/pwa/out/_next/static/chunks/5891-0f11a7d7362a9072.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/5943.264d14a44ca32513.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/604-1b3c39df083c02aa.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/6121-5f97072330738caf.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/6193.07d8ca5af4b09086.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/6203-b3fea3a60d69ff14.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/{3110.0a7065f25c841818.js → 6236.e18cced2d5ba6afb.js} +3 -3
  74. package/apps/pwa/out/_next/static/chunks/6286.d638fb5953e2e757.js +8 -0
  75. package/apps/pwa/out/_next/static/chunks/630.fe5a46428d63b013.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/6301.ae0245f507e4395e.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/6673.fbbe5699f6ea5991.js +27 -0
  78. package/apps/pwa/out/_next/static/chunks/6777-f6d301efb5de828d.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/6786.423f24c88de034c7.js +93 -0
  80. package/apps/pwa/out/_next/static/chunks/6854-cef64a948c5e122d.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/6866-79aa5b4e47cac6aa.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/6b89c8bd.24df687a820500b3.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/7037-fe27f001db30d323.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/7080.2d826860534fd88e.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/7118.8731be28c3ebb14b.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/7196.44241bc1df3e26fe.js +149 -0
  87. package/apps/pwa/out/_next/static/chunks/7235.c7c4e6cb5c693c2d.js +104 -0
  88. package/apps/pwa/out/_next/static/chunks/{7005.1623d2aea78eb85f.js → 7385.50a308a477bdfc21.js} +1 -1
  89. package/apps/pwa/out/_next/static/chunks/{6086.913a69e6fa3e4a8b.js → 7644.17e37ba6f1d4226e.js} +4 -4
  90. package/apps/pwa/out/_next/static/chunks/7706-58bfb1546637f217.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/7839.5b91455338c9fd3f.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/7857-31f13ee96f4011aa.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/81.f74cb60312fe4001.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/8219.baa715d1deee80e2.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/8256.e3ac8421a71f82f4.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/8310-444a6a895c402f12.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/8423.f7dda8aca4fcaae5.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/{7193-cf355cca5ba6c5d5.js → 8437-8d5492094b166cfa.js} +1 -1
  99. package/apps/pwa/out/_next/static/chunks/{2554.3b5e200f5f5cfcd6.js → 8444.4c26b379ca2b9596.js} +4 -4
  100. package/apps/pwa/out/_next/static/chunks/8496.75649af8652c74f4.js +226 -0
  101. package/apps/pwa/out/_next/static/chunks/8557-e7898769499cd56f.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/8576.65bb654b524f3614.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/8609.558ce2f8f9aea2be.js +64 -0
  104. package/apps/pwa/out/_next/static/chunks/8614.caec96cfb91f4ae7.js +1 -0
  105. package/apps/pwa/out/_next/static/chunks/{2547.dd27303624cb4da8.js → 8637.47f5b35afcae5e6b.js} +1 -1
  106. package/apps/pwa/out/_next/static/chunks/8714.ff0b7ce3ec3e3ae2.js +1 -0
  107. package/apps/pwa/out/_next/static/chunks/8732.367f44a916202353.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/9141-7fd1f73a5400bdc2.js +1 -0
  109. package/apps/pwa/out/_next/static/chunks/921.c4267d0d69a65404.js +1 -0
  110. package/apps/pwa/out/_next/static/chunks/{9550.d5f76828f972058c.js → 9214.63a510d39178e997.js} +2 -2
  111. package/apps/pwa/out/_next/static/chunks/9261.3962c3321b047691.js +115 -0
  112. package/apps/pwa/out/_next/static/chunks/936-26efd1a0efdd44d3.js +1 -0
  113. package/apps/pwa/out/_next/static/chunks/{8548.036c344973968847.js → 938.66ad1ef0e5abae1c.js} +1 -1
  114. package/apps/pwa/out/_next/static/chunks/{3414.172ad4ca2d9b08e8.js → 9512.5a76207a730e0491.js} +2 -2
  115. package/apps/pwa/out/_next/static/chunks/9627-4b9efe616b27e2e2.js +1 -0
  116. package/apps/pwa/out/_next/static/chunks/967-2aa39b2d04b4c690.js +1 -0
  117. package/apps/pwa/out/_next/static/chunks/9734.40eb1e558c29ccea.js +1 -0
  118. package/apps/pwa/out/_next/static/chunks/9908.3aa54f7a586a73dc.js +1 -0
  119. package/apps/pwa/out/_next/static/chunks/{6740.43de73fc427c9fe2.js → 9952.5daf5f94063a2b3e.js} +1 -1
  120. package/apps/pwa/out/_next/static/chunks/9954-ca8d29752bdbb45a.js +1 -0
  121. package/apps/pwa/out/_next/static/chunks/9974-507fa67cb94066dd.js +1 -0
  122. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-ce9e00f7fb45506a.js +1 -0
  123. package/apps/pwa/out/_next/static/chunks/app/approvals/page-fa59d6d17daa3389.js +1 -0
  124. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-53f68bbe7bd4b774.js +1 -0
  125. package/apps/pwa/out/_next/static/chunks/app/board/page-1c500fbb9f4a7a04.js +1 -0
  126. package/apps/pwa/out/_next/static/chunks/app/botlab/page-99d72673aa5c307c.js +1 -0
  127. package/apps/pwa/out/_next/static/chunks/app/bots/page-d2d5adc1e9484c88.js +1 -0
  128. package/apps/pwa/out/_next/static/chunks/app/ceo/page-c607c5aae637f1cc.js +1 -0
  129. package/apps/pwa/out/_next/static/chunks/app/chat/page-fa8c23596d555c0f.js +1 -0
  130. package/apps/pwa/out/_next/static/chunks/app/consult/page-bdab8c5e855c8416.js +1 -0
  131. package/apps/pwa/out/_next/static/chunks/app/control/page-406b47745b53f269.js +1 -0
  132. package/apps/pwa/out/_next/static/chunks/app/decisions/page-a4276386b7051f50.js +1 -0
  133. package/apps/pwa/out/_next/static/chunks/app/design-check/page-8a26bab9ad375f25.js +1 -0
  134. package/apps/pwa/out/_next/static/chunks/app/editor/page-775f48aa1dc7a2d5.js +1 -0
  135. package/apps/pwa/out/_next/static/chunks/app/exec/page-1c4dd5f959d0c558.js +1 -0
  136. package/apps/pwa/out/_next/static/chunks/app/field/page-c48938a8377c9f70.js +1 -0
  137. package/apps/pwa/out/_next/static/chunks/app/healthz/route-ce9e00f7fb45506a.js +1 -0
  138. package/apps/pwa/out/_next/static/chunks/app/inside/page-c6e7905859425aa9.js +1 -0
  139. package/apps/pwa/out/_next/static/chunks/app/intake/page-c280f4252452e830.js +1 -0
  140. package/apps/pwa/out/_next/static/chunks/app/layout-cf8c57498f20970a.js +1 -0
  141. package/apps/pwa/out/_next/static/chunks/app/live/page-ca3a17caf409fc83.js +1 -0
  142. package/apps/pwa/out/_next/static/chunks/app/live-run/{page-d7bff0f0905a662c.js → page-26e6c18a1a750cf6.js} +1 -1
  143. package/apps/pwa/out/_next/static/chunks/app/loops/page-3abc872cc6aeee39.js +1 -0
  144. package/apps/pwa/out/_next/static/chunks/app/market/page-f7481b7b2174fe3d.js +1 -0
  145. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1ae7ef8077680337.js +1 -0
  146. package/apps/pwa/out/_next/static/chunks/app/morning/page-47c5d4db587266bd.js +1 -0
  147. package/apps/pwa/out/_next/static/chunks/app/not-found-415f1e243c7ccb13.js +1 -0
  148. package/apps/pwa/out/_next/static/chunks/app/observatory/page-73f380f17b76f95c.js +1 -0
  149. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-2991073ad66ee2d9.js +1 -0
  150. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-1f5a60f70cb4dd0c.js +1 -0
  151. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fbad60d1e358fb2c.js +1 -0
  152. package/apps/pwa/out/_next/static/chunks/app/outputs/page-bac7dee94e5eae4a.js +1 -0
  153. package/apps/pwa/out/_next/static/chunks/app/page-398047d6949e8ca5.js +1 -0
  154. package/apps/pwa/out/_next/static/chunks/app/reflection/page-2d33a2231540ca89.js +1 -0
  155. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-5bf1e6bf70bcb96c.js +1 -0
  156. package/apps/pwa/out/_next/static/chunks/app/sessions/page-ba59c19376a0917d.js +1 -0
  157. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-111f187139add30f.js +1 -0
  158. package/apps/pwa/out/_next/static/chunks/app/settings/page-acaae197d5be5779.js +1 -0
  159. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-0df20b0f546a0d7d.js +1 -0
  160. package/apps/pwa/out/_next/static/chunks/app/setup/layout-ce9e00f7fb45506a.js +1 -0
  161. package/apps/pwa/out/_next/static/chunks/app/setup/page-27e1202d24b8fe35.js +1 -0
  162. package/apps/pwa/out/_next/static/chunks/app/share/page-bda78c50a78363e5.js +1 -0
  163. package/apps/pwa/out/_next/static/chunks/app/showroom/page-da592691293d9c9f.js +1 -0
  164. package/apps/pwa/out/_next/static/chunks/app/tasks/page-f832994317a2e9f0.js +1 -0
  165. package/apps/pwa/out/_next/static/chunks/app/term/{page-df3445b207ceceab.js → page-dc0a532f5b9196ec.js} +1 -1
  166. package/apps/pwa/out/_next/static/chunks/app/today/page-cfccbbeae0473404.js +1 -0
  167. package/apps/pwa/out/_next/static/chunks/app/trace/page-527e2073af84c81b.js +1 -0
  168. package/apps/pwa/out/_next/static/chunks/app/vault/page-bac86e9112671357.js +1 -0
  169. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6d59dff410e9d76a.js +1 -0
  170. package/apps/pwa/out/_next/static/chunks/app/workflows/page-53a61f8be09f02d2.js +1 -0
  171. package/apps/pwa/out/_next/static/chunks/app/workspace/page-db8f559e763c17f9.js +1 -0
  172. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c02aa4cc66455093.js +1 -0
  173. package/apps/pwa/out/_next/static/chunks/{9a8cb466.cc9fc9e6e46038aa.js → b2f6ba4d.fb56fdbbf798df33.js} +1 -1
  174. package/apps/pwa/out/_next/static/chunks/b6705a4f-b9a9047de9eecfd0.js +1 -0
  175. package/apps/pwa/out/_next/static/chunks/{4fa33ebd.21c1bd5b294496e6.js → cbe38a15.5a032598e39970c6.js} +1 -1
  176. package/apps/pwa/out/_next/static/chunks/{abb4b98b.163e0f56e84ae35d.js → d06f9160.a3d9062f95de8ad7.js} +1 -1
  177. package/apps/pwa/out/_next/static/chunks/{9b48c871.204d5d13e7809bd0.js → da5176c6.6773b6c5994e1d99.js} +1 -1
  178. package/apps/pwa/out/_next/static/chunks/framework-50e6acebc630849b.js +1 -0
  179. package/apps/pwa/out/_next/static/chunks/main-7a1ae342d5ed75e6.js +1 -0
  180. package/apps/pwa/out/_next/static/chunks/main-app-7ad7d505ba1f92b4.js +1 -0
  181. package/apps/pwa/out/_next/static/chunks/pages/_app-02edba75232cb235.js +1 -0
  182. package/apps/pwa/out/_next/static/chunks/pages/_error-97d6ebae3e8eee4a.js +1 -0
  183. package/apps/pwa/out/_next/static/chunks/webpack-e986adaa429163e8.js +1 -0
  184. package/apps/pwa/out/approvals/index.html +1 -1
  185. package/apps/pwa/out/approvals/index.txt +22 -22
  186. package/apps/pwa/out/autopilot/index.html +1 -1
  187. package/apps/pwa/out/autopilot/index.txt +22 -22
  188. package/apps/pwa/out/board/index.html +1 -1
  189. package/apps/pwa/out/board/index.txt +22 -22
  190. package/apps/pwa/out/botlab/index.html +1 -1
  191. package/apps/pwa/out/botlab/index.txt +21 -21
  192. package/apps/pwa/out/bots/index.html +1 -1
  193. package/apps/pwa/out/bots/index.txt +21 -21
  194. package/apps/pwa/out/ceo/index.html +1 -1
  195. package/apps/pwa/out/ceo/index.txt +22 -22
  196. package/apps/pwa/out/chat/index.html +1 -1
  197. package/apps/pwa/out/chat/index.txt +21 -21
  198. package/apps/pwa/out/consult/index.html +1 -1
  199. package/apps/pwa/out/consult/index.txt +22 -22
  200. package/apps/pwa/out/control/index.html +1 -1
  201. package/apps/pwa/out/control/index.txt +22 -22
  202. package/apps/pwa/out/decisions/index.html +1 -1
  203. package/apps/pwa/out/decisions/index.txt +21 -21
  204. package/apps/pwa/out/design-check/index.html +1 -1
  205. package/apps/pwa/out/design-check/index.txt +22 -22
  206. package/apps/pwa/out/editor/index.html +1 -1
  207. package/apps/pwa/out/editor/index.txt +22 -22
  208. package/apps/pwa/out/exec/index.html +1 -1
  209. package/apps/pwa/out/exec/index.txt +22 -22
  210. package/apps/pwa/out/field/index.html +1 -1
  211. package/apps/pwa/out/field/index.txt +22 -22
  212. package/apps/pwa/out/healthz +1 -1
  213. package/apps/pwa/out/index.html +1 -1
  214. package/apps/pwa/out/index.txt +21 -21
  215. package/apps/pwa/out/inside/index.html +1 -1
  216. package/apps/pwa/out/inside/index.txt +21 -21
  217. package/apps/pwa/out/intake/index.html +1 -1
  218. package/apps/pwa/out/intake/index.txt +22 -22
  219. package/apps/pwa/out/live/index.html +1 -1
  220. package/apps/pwa/out/live/index.txt +22 -22
  221. package/apps/pwa/out/live-run/index.html +1 -1
  222. package/apps/pwa/out/live-run/index.txt +22 -22
  223. package/apps/pwa/out/loops/index.html +1 -1
  224. package/apps/pwa/out/loops/index.txt +21 -21
  225. package/apps/pwa/out/market/index.html +1 -1
  226. package/apps/pwa/out/market/index.txt +21 -21
  227. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  228. package/apps/pwa/out/missions/placeholder/index.txt +21 -21
  229. package/apps/pwa/out/morning/index.html +1 -1
  230. package/apps/pwa/out/morning/index.txt +22 -22
  231. package/apps/pwa/out/observatory/index.html +1 -1
  232. package/apps/pwa/out/observatory/index.txt +22 -22
  233. package/apps/pwa/out/ops/checklist/index.html +1 -1
  234. package/apps/pwa/out/ops/checklist/index.txt +21 -21
  235. package/apps/pwa/out/ops/release/index.html +1 -1
  236. package/apps/pwa/out/ops/release/index.txt +21 -21
  237. package/apps/pwa/out/ops/seats/index.html +1 -1
  238. package/apps/pwa/out/ops/seats/index.txt +21 -21
  239. package/apps/pwa/out/outputs/index.html +1 -1
  240. package/apps/pwa/out/outputs/index.txt +21 -21
  241. package/apps/pwa/out/reflection/index.html +1 -1
  242. package/apps/pwa/out/reflection/index.txt +22 -22
  243. package/apps/pwa/out/scheduler/index.html +1 -1
  244. package/apps/pwa/out/scheduler/index.txt +22 -22
  245. package/apps/pwa/out/sessions/index.html +1 -1
  246. package/apps/pwa/out/sessions/index.txt +21 -21
  247. package/apps/pwa/out/settings/devices/index.html +1 -1
  248. package/apps/pwa/out/settings/devices/index.txt +22 -22
  249. package/apps/pwa/out/settings/index.html +1 -1
  250. package/apps/pwa/out/settings/index.txt +22 -22
  251. package/apps/pwa/out/setup/done/index.html +1 -1
  252. package/apps/pwa/out/setup/done/index.txt +22 -22
  253. package/apps/pwa/out/setup/index.html +1 -1
  254. package/apps/pwa/out/setup/index.txt +22 -22
  255. package/apps/pwa/out/share/index.html +1 -1
  256. package/apps/pwa/out/share/index.txt +22 -22
  257. package/apps/pwa/out/showroom/index.html +1 -1
  258. package/apps/pwa/out/showroom/index.txt +21 -21
  259. package/apps/pwa/out/tasks/index.html +1 -1
  260. package/apps/pwa/out/tasks/index.txt +22 -22
  261. package/apps/pwa/out/term/index.html +1 -1
  262. package/apps/pwa/out/term/index.txt +22 -22
  263. package/apps/pwa/out/today/index.html +1 -1
  264. package/apps/pwa/out/today/index.txt +22 -22
  265. package/apps/pwa/out/trace/index.html +1 -1
  266. package/apps/pwa/out/trace/index.txt +22 -22
  267. package/apps/pwa/out/vault/index.html +1 -1
  268. package/apps/pwa/out/vault/index.txt +22 -22
  269. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  270. package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
  271. package/apps/pwa/out/workflows/index.html +1 -1
  272. package/apps/pwa/out/workflows/index.txt +22 -22
  273. package/apps/pwa/out/workspace/index.html +1 -1
  274. package/apps/pwa/out/workspace/index.txt +22 -22
  275. package/apps/pwa/out/worktrees/index.html +1 -1
  276. package/apps/pwa/out/worktrees/index.txt +22 -22
  277. package/bin/elanous.mjs +3 -2
  278. package/catalog/providers/opengateway.yaml +25 -0
  279. package/docs/goal-context/01-goal-authoring-checks.md +8 -0
  280. package/docs/goal-context/03-verification-menu.md +24 -0
  281. package/docs/goal-context/04-mock-scenarios.md +100 -0
  282. package/docs/goal-context/README.md +4 -0
  283. package/graphs/catalog/catalog.yaml +1 -1
  284. package/graphs/default-loop.yaml +57 -3
  285. package/graphs/document-loop.yaml +51 -3
  286. package/graphs/implement-loop.yaml +53 -4
  287. package/graphs/journey/ta-journey.yaml +11 -1
  288. package/graphs/journey/units/author-dialogue/author-dialogue.yaml +42 -0
  289. package/graphs/journey/units/author-dialogue/observe.ts +96 -0
  290. package/graphs/journey/units/author-dialogue/recipes.yaml +10 -0
  291. package/graphs/operate-loop.yaml +51 -3
  292. package/graphs/research-loop.yaml +137 -3
  293. package/graphs/test-diet/test-diet.yaml +3 -0
  294. package/package.json +1 -1
  295. package/scripts/coord-role-pack.ts +19 -6
  296. package/scripts/hq/standby-snapshot.ts +17 -12
  297. package/scripts/release-loop/docs-land-node.ts +21 -5
  298. package/scripts/release-loop/gate-baseline-receipt.ts +155 -0
  299. package/scripts/release-loop/gate-node.ts +181 -22
  300. package/scripts/release-loop/gate-partial.ts +65 -11
  301. package/scripts/release-loop/gate-radius-live.ts +177 -0
  302. package/scripts/release-loop/gate-radius-shadow.ts +1 -1
  303. package/scripts/release-loop/node-verdict.ts +12 -1
  304. package/scripts/release-loop/npm-publish-node.ts +66 -12
  305. package/scripts/release-loop/probe-pod-host-dirs-live.ts +5 -0
  306. package/scripts/release-loop/pwa-remote.ts +166 -0
  307. package/scripts/release-loop/pwa-scenarios-node.ts +19 -3
  308. package/scripts/release-loop/resume-release.ts +41 -4
  309. package/scripts/release-loop/unattended-release.ts +2 -0
  310. package/scripts/test-diet/lib.ts +6 -1
  311. package/scripts/test-diet/node.ts +194 -13
  312. package/scripts/ux-sim.ts +39 -0
  313. package/src/agent/self-ambient-cache.ts +8 -0
  314. package/src/agent-substrate/acp-reviewer.ts +39 -4
  315. package/src/agent-substrate/pr-reviewer.ts +17 -0
  316. package/src/agent-substrate/self-review-cli.ts +62 -13
  317. package/src/autopilot/pr-manager.ts +215 -36
  318. package/src/budget/run-usage-rollup.ts +1 -0
  319. package/src/chat/index.ts +0 -2
  320. package/src/chat/pickers/modal-runtime.ts +10 -27
  321. package/src/cli/agent-cli.ts +13 -2
  322. package/src/cli/doctor-cli.ts +4 -1
  323. package/src/cli/light-entry-route.ts +46 -6
  324. package/src/cli/pod-cli.ts +77 -3
  325. package/src/cli/pr-cli.ts +5 -2
  326. package/src/cli/pr-land-pod-shadow-job.ts +3 -0
  327. package/src/cli/release-checklist-read-cli.ts +79 -0
  328. package/src/cli/release-cli.ts +72 -102
  329. package/src/cli/remote-resolve.ts +1 -1
  330. package/src/cli/root-tui-entry.ts +48 -0
  331. package/src/cli/sim-cli.ts +22 -0
  332. package/src/cli/tasks-cli.ts +540 -31
  333. package/src/cli/usage-cli.ts +13 -11
  334. package/src/clipboard/history.ts +179 -13
  335. package/src/config.ts +8 -1
  336. package/src/dashboard/chat/acp-chat.ts +8 -0
  337. package/src/dashboard/index.ts +78 -10
  338. package/src/dashboard/render/theme-resolver.ts +33 -4
  339. package/src/domains/schedule-registry.ts +16 -2
  340. package/src/git-fs/idle-refresh.ts +76 -0
  341. package/src/git-fs/index.ts +18 -7
  342. package/src/git-fs/worktree.ts +42 -4
  343. package/src/graph-kinds/registry.ts +2 -2
  344. package/src/graph-runner/runner.ts +153 -10
  345. package/src/grok/credential.ts +10 -3
  346. package/src/harness/harness-cli-command.ts +112 -28
  347. package/src/harness/harness-incidents.ts +33 -0
  348. package/src/harness/launch-utterance-ledger.ts +208 -0
  349. package/src/impl-discipline/detector.ts +16 -4
  350. package/src/impl-discipline/system-prompt.ts +11 -0
  351. package/src/impl-discipline/types.ts +2 -2
  352. package/src/index.ts +496 -143
  353. package/src/intake-plane/already-built-hint.ts +4 -2
  354. package/src/intake-plane/requirement-funnel.ts +29 -1
  355. package/src/light-cli.ts +27 -11
  356. package/src/llm/opengateway-models.ts +11 -0
  357. package/src/llm/provider-credentials.ts +14 -0
  358. package/src/llm/provider-detect.ts +8 -1
  359. package/src/llm/runtime-model-family.ts +2 -1
  360. package/src/llm.ts +213 -56
  361. package/src/model-tier/llm-tier-map.ts +15 -1
  362. package/src/native-tool-catalog.ts +15 -1
  363. package/src/nexus/api/loop-resources.ts +13 -1
  364. package/src/oauth/codex.ts +7 -0
  365. package/src/ops/overview-collect.ts +18 -3
  366. package/src/ops/overview.ts +20 -7
  367. package/src/pr-tool/census.ts +261 -27
  368. package/src/pty-shell/pty-manifest.ts +7 -0
  369. package/src/release-loop/checklist.ts +24 -0
  370. package/src/release-loop/landed-but-yellow.ts +129 -5
  371. package/src/release-loop/light-rc.ts +1 -0
  372. package/src/release-loop/rehearsal.ts +2 -2
  373. package/src/repl/index.ts +174 -17
  374. package/src/repl/ops-msg-bridge.ts +32 -0
  375. package/src/self-dev/ask-launch-flow.ts +44 -5
  376. package/src/self-dev/cli-help-text.ts +17 -0
  377. package/src/self-dev/dev-cli.ts +11 -9
  378. package/src/self-dev/draft-residue.ts +37 -1
  379. package/src/self-dev/draft-sweep.ts +137 -11
  380. package/src/self-dev/draft-triage-rules.ts +107 -0
  381. package/src/self-dev/fleet-reduce.ts +3 -2
  382. package/src/self-dev/graph-front-nodes.ts +112 -0
  383. package/src/self-dev/graph-journey-nodes.ts +1 -0
  384. package/src/self-dev/harness-run-cli.ts +3 -15
  385. package/src/self-dev/orchestrate-cli-text.ts +10 -0
  386. package/src/self-dev/review-provider-fallback.ts +35 -1
  387. package/src/self-dev/self-orchestrate-runtime.ts +1 -11
  388. package/src/self-dev/shard-aggregate.ts +42 -5
  389. package/src/self-dev/shard-run.ts +8 -1
  390. package/src/self-implement/blocked-draft-policy.ts +20 -0
  391. package/src/self-implement/classify-shadow.ts +3 -7
  392. package/src/self-implement/doctor-visit.ts +18 -1
  393. package/src/self-implement/gate-baseline.ts +56 -17
  394. package/src/self-implement/gate-cli.ts +1 -0
  395. package/src/self-implement/gate-remote.ts +31 -20
  396. package/src/self-implement/goal-author-decompose.ts +275 -47
  397. package/src/self-implement/goal-author.ts +250 -0
  398. package/src/self-implement/goal-elaborate.ts +295 -0
  399. package/src/self-implement/goal-refine.ts +270 -0
  400. package/src/self-implement/graph-overlay-yaml.ts +14 -3
  401. package/src/self-implement/orchestrator.ts +152 -45
  402. package/src/self-implement/rework-budget-judgment.ts +5 -8
  403. package/src/self-implement/run-stop.ts +10 -1
  404. package/src/self-implement/running-runs.ts +29 -1
  405. package/src/self-implement/scope-adjacency.ts +113 -0
  406. package/src/self-implement/seams.ts +127 -24
  407. package/src/self-implement/stop-autoheal.ts +87 -1
  408. package/src/session-runtime/index.ts +1 -1
  409. package/src/setup/llm-env-detect.ts +5 -0
  410. package/src/skills/tools/agent.ts +140 -14
  411. package/src/skills/tools/msg-ask.ts +100 -0
  412. package/src/skills/tools/ops-ledger.ts +2 -1
  413. package/src/steward/hold-hygiene.ts +24 -6
  414. package/src/steward/triage-budget.ts +194 -0
  415. package/src/steward/triage.ts +20 -3
  416. package/src/task-agent/actions.ts +69 -1
  417. package/src/task-agent/green-sweep.ts +4 -0
  418. package/src/task-agent/live-moves.ts +160 -4
  419. package/src/task-agent/parent-host.ts +123 -3
  420. package/src/task-agent/shadow.ts +7 -1
  421. package/src/task-agent/task-hand.ts +54 -41
  422. package/src/task-agent/tier-split.ts +127 -0
  423. package/src/task-agent/work-order-build.ts +2 -0
  424. package/src/task-agent/work-order.ts +3 -2
  425. package/src/task-orchestrator/generator-prompt.ts +60 -11
  426. package/src/task-orchestrator/surfaces/pod-account-broker.ts +33 -0
  427. package/src/task-orchestrator/surfaces/pod-child-providers.ts +1 -1
  428. package/src/task-orchestrator/surfaces/pod-command-job.ts +45 -7
  429. package/src/task-orchestrator/surfaces/pod-image-source.ts +89 -1
  430. package/src/task-orchestrator/surfaces/pod-lease.ts +17 -8
  431. package/src/task-orchestrator/surfaces/pod-manifest-test-normalize.ts +8 -2
  432. package/src/task-orchestrator/surfaces/pod-provenance.ts +76 -0
  433. package/src/task-orchestrator/surfaces/pod-source-receive.ts +18 -0
  434. package/src/task-orchestrator/surfaces/pod-warm-provenance.ts +81 -0
  435. package/src/task-orchestrator/surfaces/pod-warm-worker-pod.ts +6 -2
  436. package/src/task-orchestrator/surfaces/pod-warm-worker-protocol.ts +3 -0
  437. package/src/task-orchestrator/surfaces/pod-warm-worker-provider.ts +37 -5
  438. package/src/task-orchestrator/surfaces/self-implement-pod.ts +368 -36
  439. package/src/task-orchestrator/surfaces/self-implement.ts +23 -0
  440. package/src/tool-runtime/ops-ledger-runtimes.ts +10 -2
  441. package/src/tui/README.md +2 -0
  442. package/src/tui/chat/background-job-status.ts +54 -0
  443. package/src/tui/chat/log-view.ts +80 -0
  444. package/src/tui/entry.ts +406 -0
  445. package/src/tui/input/composer.ts +144 -0
  446. package/src/tui/seat-mail-boot.ts +59 -0
  447. package/src/tui/slash/compact.ts +100 -0
  448. package/src/tui/slash/new-session.ts +35 -0
  449. package/src/tui-client/local-chat-turn.ts +239 -0
  450. package/src/tui-client/local-harness-command.ts +63 -0
  451. package/src/tui-client/local-provider-command.ts +184 -0
  452. package/src/user-config.ts +4 -2
  453. package/src/ux-sim/event-monkey.ts +378 -0
  454. package/src/ux-sim/fake-bin.ts +42 -7
  455. package/src/ux-sim/fault-nextmove-dispatch.ts +266 -0
  456. package/src/ux-sim/fault-pod-codex-503.ts +211 -0
  457. package/src/ux-sim/fault-scope-escape-lift.ts +189 -0
  458. package/src/version/packed-revision.json +1 -1
  459. package/apps/pwa/out/_next/static/chunks/1162.c69abda138c7ce43.js +0 -79
  460. package/apps/pwa/out/_next/static/chunks/1449-1fede62af84c3e43.js +0 -1
  461. package/apps/pwa/out/_next/static/chunks/1598-13fc845935e816df.js +0 -1
  462. package/apps/pwa/out/_next/static/chunks/1639.62c5e16fa20d242f.js +0 -94
  463. package/apps/pwa/out/_next/static/chunks/1661-adb8b2babc9aaf8c.js +0 -1
  464. package/apps/pwa/out/_next/static/chunks/2147.107c0fc6146fb15a.js +0 -23
  465. package/apps/pwa/out/_next/static/chunks/2202-ef1c9b000c3074fc.js +0 -1
  466. package/apps/pwa/out/_next/static/chunks/2247.1e326638ee43e672.js +0 -115
  467. package/apps/pwa/out/_next/static/chunks/2268.56e1e3d58180abae.js +0 -8
  468. package/apps/pwa/out/_next/static/chunks/2469-606819c812414021.js +0 -1
  469. package/apps/pwa/out/_next/static/chunks/2593.e53c1620c0bb3599.js +0 -1
  470. package/apps/pwa/out/_next/static/chunks/2690.e96671114222676a.js +0 -1
  471. package/apps/pwa/out/_next/static/chunks/2898-79ee1a5cc0cd9d13.js +0 -1
  472. package/apps/pwa/out/_next/static/chunks/298.26099a9f65e5c8ff.js +0 -1
  473. package/apps/pwa/out/_next/static/chunks/2992.51fc2a7295bd8139.js +0 -1
  474. package/apps/pwa/out/_next/static/chunks/3001-5a52788c813b6113.js +0 -1
  475. package/apps/pwa/out/_next/static/chunks/3014-291245fba9df7b5b.js +0 -1
  476. package/apps/pwa/out/_next/static/chunks/3068.ee78201f0251a18d.js +0 -1
  477. package/apps/pwa/out/_next/static/chunks/3083.cd399a17e9365f78.js +0 -1
  478. package/apps/pwa/out/_next/static/chunks/3103.84470c8445962f04.js +0 -34
  479. package/apps/pwa/out/_next/static/chunks/3117-e1c474683fd1f824.js +0 -1
  480. package/apps/pwa/out/_next/static/chunks/3251-2c17bd5a91f8de2e.js +0 -1
  481. package/apps/pwa/out/_next/static/chunks/326.3fb32322bb2ddf6d.js +0 -1
  482. package/apps/pwa/out/_next/static/chunks/3411-f87314c6c66ebc73.js +0 -1
  483. package/apps/pwa/out/_next/static/chunks/3421-7ff7fbaa156985a7.js +0 -1
  484. package/apps/pwa/out/_next/static/chunks/3498.cf2a15a8a9e5b926.js +0 -226
  485. package/apps/pwa/out/_next/static/chunks/381.f55dc63ea87735e6.js +0 -1
  486. package/apps/pwa/out/_next/static/chunks/3819.163d4777f0a597f6.js +0 -32
  487. package/apps/pwa/out/_next/static/chunks/3837.722e053fb9f321ad.js +0 -1
  488. package/apps/pwa/out/_next/static/chunks/4129-ab0a6189f1778633.js +0 -1
  489. package/apps/pwa/out/_next/static/chunks/4361.93d14ba594b3a035.js +0 -104
  490. package/apps/pwa/out/_next/static/chunks/4364.7a7eb19cceb67d2f.js +0 -286
  491. package/apps/pwa/out/_next/static/chunks/4406.d71f506f7c3ee9d3.js +0 -149
  492. package/apps/pwa/out/_next/static/chunks/4461.2ad1514180ae7e29.js +0 -148
  493. package/apps/pwa/out/_next/static/chunks/4479.bd309cbe0e3842ee.js +0 -64
  494. package/apps/pwa/out/_next/static/chunks/4522-05966f777d66d13b.js +0 -1
  495. package/apps/pwa/out/_next/static/chunks/4543-9a3e9812e845de65.js +0 -1
  496. package/apps/pwa/out/_next/static/chunks/4557.f8f1d84a0d90d590.js +0 -74
  497. package/apps/pwa/out/_next/static/chunks/4589-79b11542179ef58c.js +0 -1
  498. package/apps/pwa/out/_next/static/chunks/4633-9f8f9f88ffbb8f5c.js +0 -1
  499. package/apps/pwa/out/_next/static/chunks/4831.23af4d10c0697b5e.js +0 -1
  500. package/apps/pwa/out/_next/static/chunks/4983.5951b85724f61f2a.js +0 -1
  501. package/apps/pwa/out/_next/static/chunks/5028-f0722e6114c313e7.js +0 -1
  502. package/apps/pwa/out/_next/static/chunks/5249.96a848656b582a93.js +0 -1
  503. package/apps/pwa/out/_next/static/chunks/5311.16e0ffbfa5468b80.js +0 -62
  504. package/apps/pwa/out/_next/static/chunks/5446.edf141cff9ef647d.js +0 -1
  505. package/apps/pwa/out/_next/static/chunks/5489.ec2dd1b5272a19dc.js +0 -24
  506. package/apps/pwa/out/_next/static/chunks/559.df87695df637920c.js +0 -1
  507. package/apps/pwa/out/_next/static/chunks/5618-5c88f2f70178c4ad.js +0 -1
  508. package/apps/pwa/out/_next/static/chunks/5764.05487bf0b73ddb6b.js +0 -1
  509. package/apps/pwa/out/_next/static/chunks/5930.e678826c3f831aab.js +0 -1
  510. package/apps/pwa/out/_next/static/chunks/5dfb57f7.f2e3305326202474.js +0 -1
  511. package/apps/pwa/out/_next/static/chunks/6200-cefd6f33f59d765e.js +0 -1
  512. package/apps/pwa/out/_next/static/chunks/6314-edeba650ae686ac9.js +0 -1
  513. package/apps/pwa/out/_next/static/chunks/6316-bc9a9b74d7842d79.js +0 -1
  514. package/apps/pwa/out/_next/static/chunks/6476-13b9779b73528008.js +0 -1
  515. package/apps/pwa/out/_next/static/chunks/666-04db3ac4c8e3c66f.js +0 -1
  516. package/apps/pwa/out/_next/static/chunks/670.682e55106ef915a6.js +0 -77
  517. package/apps/pwa/out/_next/static/chunks/6816.b7ba9d0a1a4a46a7.js +0 -1
  518. package/apps/pwa/out/_next/static/chunks/690-fbbcc777f189af37.js +0 -1
  519. package/apps/pwa/out/_next/static/chunks/6989.0d3d4020e5cb3df6.js +0 -90
  520. package/apps/pwa/out/_next/static/chunks/7217-d17393fe7af78fa6.js +0 -1
  521. package/apps/pwa/out/_next/static/chunks/7232.24edbd938a69c8d0.js +0 -1
  522. package/apps/pwa/out/_next/static/chunks/7295cd1e.b4b557908888eeee.js +0 -1
  523. package/apps/pwa/out/_next/static/chunks/7323.adb02de21991883c.js +0 -1
  524. package/apps/pwa/out/_next/static/chunks/7448.478958ffbbba0ac0.js +0 -1
  525. package/apps/pwa/out/_next/static/chunks/7537.ba2208c5c656b8a3.js +0 -1
  526. package/apps/pwa/out/_next/static/chunks/7562.948255cbc3e79d33.js +0 -41
  527. package/apps/pwa/out/_next/static/chunks/7596-962ae6db6cfc9c83.js +0 -1
  528. package/apps/pwa/out/_next/static/chunks/7671.cff51beac212a2ba.js +0 -1
  529. package/apps/pwa/out/_next/static/chunks/7883.3916734ee16d3e58.js +0 -27
  530. package/apps/pwa/out/_next/static/chunks/7952.b192c31b6a0e00cc.js +0 -1
  531. package/apps/pwa/out/_next/static/chunks/801.5b24e81eded22d1b.js +0 -1
  532. package/apps/pwa/out/_next/static/chunks/8176.8c9a8613f5a01ac1.js +0 -36
  533. package/apps/pwa/out/_next/static/chunks/819e78a6.fa3dd7f7da19bf61.js +0 -1
  534. package/apps/pwa/out/_next/static/chunks/8220-78978450c3183d8d.js +0 -1
  535. package/apps/pwa/out/_next/static/chunks/8221-eb60b5bc692a3361.js +0 -1
  536. package/apps/pwa/out/_next/static/chunks/826.e9a26064f474949e.js +0 -1
  537. package/apps/pwa/out/_next/static/chunks/8459-a351487a4480f584.js +0 -1
  538. package/apps/pwa/out/_next/static/chunks/8482-75823d513a4ce151.js +0 -1
  539. package/apps/pwa/out/_next/static/chunks/8488.8da515248cda3f3a.js +0 -1
  540. package/apps/pwa/out/_next/static/chunks/8584.388a1779d9b203af.js +0 -1
  541. package/apps/pwa/out/_next/static/chunks/8637.a55e098e61ffefb8.js +0 -1
  542. package/apps/pwa/out/_next/static/chunks/8817.61bb7ffbba502ecb.js +0 -1
  543. package/apps/pwa/out/_next/static/chunks/9207-8e6a12db30373f32.js +0 -1
  544. package/apps/pwa/out/_next/static/chunks/9263-00ae932c62696a86.js +0 -1
  545. package/apps/pwa/out/_next/static/chunks/9318.caafc3a130e8769c.js +0 -1
  546. package/apps/pwa/out/_next/static/chunks/932.da3fb55951882562.js +0 -93
  547. package/apps/pwa/out/_next/static/chunks/9433.002f8426ac75b6b7.js +0 -1
  548. package/apps/pwa/out/_next/static/chunks/9464.dd631a5a14b1d660.js +0 -24
  549. package/apps/pwa/out/_next/static/chunks/9739.8d5f36ac67a3b236.js +0 -1
  550. package/apps/pwa/out/_next/static/chunks/9743.beeabb9d5f86abe4.js +0 -1
  551. package/apps/pwa/out/_next/static/chunks/9843-a23d3f32dc43f715.js +0 -1
  552. package/apps/pwa/out/_next/static/chunks/994-28211077c7c587d5.js +0 -1
  553. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-3b4c947f174e33d8.js +0 -1
  554. package/apps/pwa/out/_next/static/chunks/app/approvals/page-369d5ce99483f859.js +0 -1
  555. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-63c0ed0eb26e5fce.js +0 -1
  556. package/apps/pwa/out/_next/static/chunks/app/board/page-62802c6003bc85cc.js +0 -1
  557. package/apps/pwa/out/_next/static/chunks/app/botlab/page-ecb41560d4e77be9.js +0 -1
  558. package/apps/pwa/out/_next/static/chunks/app/bots/page-7b65ad8130c93e95.js +0 -1
  559. package/apps/pwa/out/_next/static/chunks/app/ceo/page-8755477c15a9060c.js +0 -1
  560. package/apps/pwa/out/_next/static/chunks/app/chat/page-5725447dd972c471.js +0 -1
  561. package/apps/pwa/out/_next/static/chunks/app/consult/page-b979de0591a69375.js +0 -1
  562. package/apps/pwa/out/_next/static/chunks/app/control/page-6d0796bf4633751a.js +0 -1
  563. package/apps/pwa/out/_next/static/chunks/app/decisions/page-59c31dc0394e67b4.js +0 -1
  564. package/apps/pwa/out/_next/static/chunks/app/design-check/page-12dfc95f6cc5c718.js +0 -1
  565. package/apps/pwa/out/_next/static/chunks/app/editor/page-25788244e66aa4dd.js +0 -1
  566. package/apps/pwa/out/_next/static/chunks/app/exec/page-a2555dc705466322.js +0 -1
  567. package/apps/pwa/out/_next/static/chunks/app/field/page-4fb31eb476dcbad3.js +0 -1
  568. package/apps/pwa/out/_next/static/chunks/app/healthz/route-3b4c947f174e33d8.js +0 -1
  569. package/apps/pwa/out/_next/static/chunks/app/inside/page-578729adbeaa40cb.js +0 -1
  570. package/apps/pwa/out/_next/static/chunks/app/intake/page-a1d20fffbec35205.js +0 -1
  571. package/apps/pwa/out/_next/static/chunks/app/layout-4a558f94516b8004.js +0 -1
  572. package/apps/pwa/out/_next/static/chunks/app/live/page-9e3834705b86145e.js +0 -1
  573. package/apps/pwa/out/_next/static/chunks/app/loops/page-cb1c5fe746ea4014.js +0 -1
  574. package/apps/pwa/out/_next/static/chunks/app/market/page-9ee320507356582a.js +0 -1
  575. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66fd183b74835886.js +0 -1
  576. package/apps/pwa/out/_next/static/chunks/app/morning/page-3272a064a01a9927.js +0 -1
  577. package/apps/pwa/out/_next/static/chunks/app/not-found-f281b7a7f6ef2529.js +0 -1
  578. package/apps/pwa/out/_next/static/chunks/app/observatory/page-d45a65f8b7124fcd.js +0 -1
  579. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-e9c1fe684dd19a66.js +0 -1
  580. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-b8322932fc6e34de.js +0 -1
  581. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-82c66d9af2290575.js +0 -1
  582. package/apps/pwa/out/_next/static/chunks/app/outputs/page-63c6752009c97b05.js +0 -1
  583. package/apps/pwa/out/_next/static/chunks/app/page-bb33d42ebc358aec.js +0 -1
  584. package/apps/pwa/out/_next/static/chunks/app/reflection/page-7e4c9379197d7214.js +0 -1
  585. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-38bf1e9a7001732b.js +0 -1
  586. package/apps/pwa/out/_next/static/chunks/app/sessions/page-2e5b752012389d56.js +0 -1
  587. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-398c0faa173203f7.js +0 -1
  588. package/apps/pwa/out/_next/static/chunks/app/settings/page-002f53a965b6445c.js +0 -1
  589. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-7f75997f61a26269.js +0 -1
  590. package/apps/pwa/out/_next/static/chunks/app/setup/layout-3b4c947f174e33d8.js +0 -1
  591. package/apps/pwa/out/_next/static/chunks/app/setup/page-05631753294f23fb.js +0 -1
  592. package/apps/pwa/out/_next/static/chunks/app/share/page-edd700fd2cc3d7f8.js +0 -1
  593. package/apps/pwa/out/_next/static/chunks/app/showroom/page-105f83ecb4594365.js +0 -1
  594. package/apps/pwa/out/_next/static/chunks/app/tasks/page-5823e87d2017f19c.js +0 -1
  595. package/apps/pwa/out/_next/static/chunks/app/today/page-b51ca09566c03de7.js +0 -1
  596. package/apps/pwa/out/_next/static/chunks/app/trace/page-8aacd5ded5dbac60.js +0 -1
  597. package/apps/pwa/out/_next/static/chunks/app/vault/page-bae3365a0b3c6a8e.js +0 -1
  598. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-9ed52b48ff1aa7fd.js +0 -1
  599. package/apps/pwa/out/_next/static/chunks/app/workflows/page-b6f8cad35fe00946.js +0 -1
  600. package/apps/pwa/out/_next/static/chunks/app/workspace/page-6d25ceda500d2457.js +0 -1
  601. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-b6401a5f2c962fe8.js +0 -1
  602. package/apps/pwa/out/_next/static/chunks/ebf6cf5e.0d2f887b040e80fb.js +0 -122
  603. package/apps/pwa/out/_next/static/chunks/f09ec7d7-d29abe23edcbb193.js +0 -1
  604. package/apps/pwa/out/_next/static/chunks/ffdc3a04.b9c23c1b28dd4a96.js +0 -1
  605. package/apps/pwa/out/_next/static/chunks/framework-9322cb8a56ac081c.js +0 -1
  606. package/apps/pwa/out/_next/static/chunks/main-2fbab294e49a1561.js +0 -1
  607. package/apps/pwa/out/_next/static/chunks/main-app-f36729f56621a8f3.js +0 -1
  608. package/apps/pwa/out/_next/static/chunks/pages/_app-2a62e450b47c6114.js +0 -1
  609. package/apps/pwa/out/_next/static/chunks/pages/_error-5e17f7b445f34d43.js +0 -1
  610. package/apps/pwa/out/_next/static/chunks/webpack-b4263e2190ea7adf.js +0 -1
  611. /package/apps/pwa/out/_next/static/{L04y73dlwKeTW-GetaTF3 → T1mBQqmkwKDLGtyisDCOd}/_ssgManifest.js +0 -0
@@ -18,6 +18,8 @@ import { GROUNDING_TOKEN_ENV, GROUNDING_URL_ENV, mintGroundingToken, revokeGroun
18
18
  import { POD_CREDENTIAL_GROK_PATH, POD_CREDENTIAL_GITHUB_PATH, POD_GITHUB_CREDENTIAL_TOKEN_ENV, POD_GITHUB_CREDENTIAL_URL_ENV, installationRepositories } from '../../nexus/api/pod-credential-api.js';
19
19
  import { coalescedInstallationCredential } from '../../auth/github-app-token.js';
20
20
  import { POD_CREDENTIAL_TOKEN_ENV, POD_CREDENTIAL_URL_ENV } from '../../grok/credential.js';
21
+ import { isVerifiedOpenGatewayModel } from '../../llm/opengateway-models.js';
22
+ import { configuredOpenGatewayKeys } from '../../llm/provider-credentials.js';
21
23
  import { collectPodLedgers, createPodLedgerFollower } from './pod-ledger-collect.js';
22
24
  import { collectPodArtifacts, podNoResultDiagnostic } from './pod-artifact-return.js';
23
25
  import { readPodRepairArtifact } from './pod-repair-commit.js';
@@ -29,6 +31,7 @@ import { finishPodFragment, writePodFragment } from '../../harness/self-send-tar
29
31
  import { mintRunId, normalizeRunId } from '../../harness/harness-space.js';
30
32
  import { TA_LAND_ENV } from '../../task-agent/task-hand.js';
31
33
  import { appendRunLedgerEntry, loadRunLedger, runLedgerDir, runLedgerPath } from '../../self-implement/run-ledger.js';
34
+ import { runSuffixKey } from '../../self-implement/salvage-branches.js';
32
35
  import type { GoalExecutionRecord } from '../../self-implement/orchestrator.js';
33
36
  import { hostRepositoryUnavailable, runHostRegate, type HostRegateResult } from '../../self-implement/host-regate.js';
34
37
  import { resolvePodHostAcpReview, runPodHostAcpReview, type PodHostAcpReviewOutcome } from './pod-host-acp-review.js';
@@ -68,7 +71,7 @@ import { isAbsolute, join, normalize, relative, resolve, sep } from 'node:path';
68
71
  import { findGitDir } from '../../git-fs/locate.js';
69
72
  import { hostLeaseBaseDir } from '../../pod-lease/host-lease.js';
70
73
  import { acquireLockAsync } from '../../storage/file-lock.js';
71
- import { debug } from '../../debug/log.js';
74
+ import { debug, redactSecretText } from '../../debug/log.js';
72
75
  import { enterJourneyNode, exitJourneyNode, passJourneyNode } from '../../self-dev/graph-journey-nodes.js';
73
76
  import { routeJourneyEdge } from '../../self-dev/graph-journey-route.js';
74
77
  import { selectLiveDetail } from '../../live/detail-switch.js';
@@ -103,6 +106,8 @@ import type { WarmWorkerCommand } from './pod-warm-worker-protocol.js';
103
106
  import type { WarmPodTransportResult } from './pod-warm-worker-pod.js';
104
107
 
105
108
  export type Kubectl = (args: readonly string[], input?: string) => { status: number | null; stdout: string; stderr: string };
109
+ type OpenPrByBranch = { status: 'found'; prNumber: number; prUrl: string }
110
+ | { status: 'not-found' | 'failed'; reason: string };
106
111
 
107
112
  /** TC supplies the validated candidate/lease transport. UX only forwards its request command. */
108
113
  export type WarmPodRequestHook = (request: { command: WarmWorkerCommand }) => Promise<WarmPodTransportResult> | WarmPodTransportResult;
@@ -169,6 +174,8 @@ export interface PodSpawnOptions {
169
174
  authorConfig?: () => UserConfig;
170
175
  /** Host fallback chain seam; a Pod has no access to the host config. */
171
176
  fallbackConfig?: () => UserConfig;
177
+ /** Host OpenGateway key config seam; unit tests inject isolated config without bypassing production reads. */
178
+ openGatewayConfig?: () => UserConfig;
172
179
  /** 원천 — 없으면 종전 `git clone --depth 50`. bundle 이면 apply 뒤 kubectl cp 로 싣는다. */
173
180
  source?: PodSource;
174
181
  /** Existing PR repair: pin the source head and export a commit bundle instead of opening a PR. */
@@ -205,6 +212,8 @@ export interface PodSpawnOptions {
205
212
  taskAgentLiveMoves?: () => Promise<ReadonlySet<string>> | ReadonlySet<string>;
206
213
  /** PR comment for host-regate failures that never reached runHostRegate (injectable for tests). */
207
214
  ghComment?: (prNumber: number, body: string) => void;
215
+ /** Read-only host gh lookup; injected fixtures can stand in for gh without creating or closing a PR. */
216
+ lookupOpenPrByBranch?: (branch: string, repoUrl: string) => Promise<OpenPrByBranch> | OpenPrByBranch;
208
217
  draftResidue?: (record: PrTerminalRecord, ctx: DraftResidueContext) => Promise<void> | void;
209
218
  /** 🔑 Pod 필수 스킬의 키(.env)를 이 런의 Secret 으로 넘긴다 — 명시 opt-in(유료 크레딧을 쓴다). */
210
219
  skillEnv?: boolean;
@@ -397,6 +406,11 @@ export async function podCodexCredentialWithPrerefresh(account: string, deps: {
397
406
  // ⑴ 잠금을 기다리는 사이 동료 런이 갱신했으면 그대로 쓴다 — 두 번째 갱신이 회전된 토큰으로 실패하는 자리.
398
407
  const first = reread();
399
408
  if (!('expiringH' in first)) { observe('refresh-skipped-already-fresh', null); return first; }
409
+ // The lease can transfer while an account lock is queued. Recheck inside the
410
+ // critical section, immediately before the only refresh-token operation.
411
+ let stillHolder = false;
412
+ try { stillHolder = await deps.isLeaseHolder(); } catch { stillHolder = false; }
413
+ if (!stillHolder) { observe('no-lease', null, 'lease-lost-before-refresh'); return null; }
400
414
  let refreshed: Awaited<ReturnType<typeof deps.refresh>> | null = null;
401
415
  let failReason = 'threw';
402
416
  try { refreshed = await deps.refresh(account); }
@@ -430,6 +444,7 @@ export async function podCodexCredentialWithPrerefresh(account: string, deps: {
430
444
 
431
445
  /** 기본 임대 판정 — 기존 HQ 펜스(`ledger-cli` 역할 · fail-open 없음)를 그대로 쓴다. */
432
446
  async function defaultHqLeaseHolder(): Promise<boolean> {
447
+ if (process.env.ELANOUS_REMOTE_CODEX_ACCESS_ONLY === '1') return false;
433
448
  const { hqFenceDecision } = await import('../../hq/hq.js');
434
449
  return hqFenceDecision('ledger-cli').run;
435
450
  }
@@ -649,20 +664,45 @@ function githubRepositoryFromUrl(repoUrl: string): string | null {
649
664
  return owner && name && ![owner, name].some((part) => part === '.' || part === '..') ? `${owner}/${name}` : null;
650
665
  }
651
666
 
652
- /** GROK-CHILD-GIT-GUARD — the Pod holds a GitHub write token and our repo cannot protect main (private free plan · API 403),
653
- * so a child tool could `git push origin main`, force-push another run's branch or delete remote branches. A global
654
- * pre-push hook lets a Pod push only its own run branches (self-impl/* · salvage/* · bench/<id>/<arm>) and refuses
655
- * everything else, including deletes and the shared bench base. It stops accidental and reset-then-push overwrites;
656
- * it is not a sandbox — credential isolation from child tools is the next slice. */
667
+ /** GROK-CHILD-GIT-GUARD — the Pod holds a GitHub write token and our repo cannot protect main (private free plan · API 403).
668
+ * The global hook refuses main/tags/unknown refs and most deletes. With a runId, self-impl refs use the same six-hex
669
+ * run suffix as planned branch names; only that suffix and this run's disposable PR scratch create/delete pass.
670
+ * A same-card PR reuse push is the one exception: per-command Git config binds its ref, old head and card;
671
+ * the hook compares the Pod card and proves fast-forward ancestry before accepting it.
672
+ * salvage/* and bench arm refs keep their existing broader rules. A six-hex collision remains possible, so this is
673
+ * an accidental overwrite guard, not a security isolation boundary; child credential isolation is a separate slice. */
657
674
  export const POD_GIT_HOOKS_DIR = '$HOME/.elanous-git-hooks';
658
- export function podGitPushGuardScript(): string {
675
+ export function podGitPushGuardScript(runId?: string): string {
676
+ const runKey = runId ? runSuffixKey(runId) : null;
677
+ const zeroSha = '0'.repeat(40);
659
678
  return [
660
679
  `mkdir -p "${POD_GIT_HOOKS_DIR}" && cat > "${POD_GIT_HOOKS_DIR}/pre-push" <<'ELANOUS_PRE_PUSH'`,
661
680
  '#!/bin/sh',
662
681
  'while read -r lref lsha rref rsha; do',
663
- ' case "$lsha" in *[!0]*) ;; *) echo "[pod] git-guard: refused remote delete $rref" >&2; exit 1 ;; esac',
682
+ ' case "$lsha" in *[!0]*) ;; *)',
683
+ ...(runKey ? [
684
+ ` case "$rref" in refs/heads/self-impl/pr-create-r${runKey}-*) ;; *) echo "[pod] git-guard: refused remote delete $rref" >&2; exit 1 ;; esac ;;`,
685
+ ] : [' echo "[pod] git-guard: refused remote delete $rref" >&2; exit 1 ;;']),
686
+ ' esac',
664
687
  ' case "$rref" in',
665
- ' refs/heads/self-impl/*|refs/heads/salvage/*) ;;',
688
+ ...(runKey ? [
689
+ ` refs/heads/self-impl/pr-create-r${runKey}-*)`,
690
+ ` case "$lsha:$rsha" in ${zeroSha}:*|*:${zeroSha}) ;; *) echo "[pod] git-guard: refused scratch ref update $rref" >&2; exit 1 ;; esac ;;`,
691
+ ` refs/heads/self-impl/*-r${runKey}|refs/heads/self-impl/*-r${runKey}-early) ;;`,
692
+ ' refs/heads/self-impl/*)',
693
+ ' reuse_ref=$(git config --get elanous.sameCellReuseRef 2>/dev/null || true)',
694
+ ' reuse_head=$(git config --get elanous.sameCellReuseHead 2>/dev/null || true)',
695
+ ' reuse_card=$(git config --get elanous.sameCellReuseCard 2>/dev/null || true)',
696
+ ' if [ -z "$reuse_ref" ]; then echo "[pod] git-guard: refused push to $rref (another run)" >&2; exit 1; fi',
697
+ ' if [ "$rref" != "$reuse_ref" ]; then echo "[pod] git-guard: refused same-cell ref-mismatch $rref" >&2; exit 1; fi',
698
+ ' if [ -z "${ELANOUS_TASK_CARD_ID:-}" ] || [ "$reuse_card" != "$ELANOUS_TASK_CARD_ID" ]; then echo "[pod] git-guard: refused same-cell card-mismatch $rref" >&2; exit 1; fi',
699
+ ' if [ -z "$reuse_head" ] || [ "$rsha" != "$reuse_head" ]; then echo "[pod] git-guard: refused same-cell remote-head-mismatch $rref" >&2; exit 1; fi',
700
+ ' if ! git merge-base --is-ancestor "$rsha" "$lsha" 2>/dev/null; then echo "[pod] git-guard: refused same-cell non-fast-forward $rref" >&2; exit 1; fi',
701
+ ' echo "[pod] git-guard: allowed same-cell fast-forward $rref" >&2 ;;',
702
+ ] : [
703
+ ...(runId ? [' refs/heads/self-impl/*) echo "[pod] git-guard: refused push to $rref (invalid run id)" >&2; exit 1 ;;'] : [' refs/heads/self-impl/*) ;;']),
704
+ ]),
705
+ ' refs/heads/salvage/*) ;;',
666
706
  ' refs/heads/bench/*/base) echo "[pod] git-guard: refused push to shared bench base $rref" >&2; exit 1 ;;',
667
707
  ' refs/heads/bench/*/*) ;;',
668
708
  ' *) echo "[pod] git-guard: refused push to $rref (only this run\'s branches)" >&2; exit 1 ;;',
@@ -963,7 +1003,7 @@ export function podMemoryLimitFor(feature: string, env: NodeJS.ProcessEnv | Read
963
1003
  }
964
1004
 
965
1005
  /** A named child provider decides the Pod provider; paid OpenRouter requires an explicit key and model. */
966
- export function podNamedChildProvider(named: string | undefined, credential: { grokSubscription: boolean; grokApiKey: boolean; openrouterKey?: boolean; anthropicKey?: boolean }, childModel?: string): { provider?: PodChildProvider; refuse?: string } {
1006
+ export function podNamedChildProvider(named: string | undefined, credential: { grokSubscription: boolean; grokApiKey: boolean; openrouterKey?: boolean; openGatewayKey?: boolean; anthropicKey?: boolean }, childModel?: string): { provider?: PodChildProvider; refuse?: string } {
967
1007
  // 공백·빈 이름 = «미지정»(종전 기본 경로) — 유료 provider 로 흘러가지 않는다(codex 재리뷰 must-fix).
968
1008
  if (!named?.trim()) return {};
969
1009
  // ⭐ 목록은 한 곳(pod-child-providers.ts) — 발사 관문도 같은 상수·같은 문면으로 거부한다.
@@ -978,6 +1018,12 @@ export function podNamedChildProvider(named: string | undefined, credential: { g
978
1018
  if (!childModel?.trim()) return { refuse: 'pod: openrouter 자식 모델 없음 — --child-llm-model 필요' };
979
1019
  return { provider: 'openrouter' };
980
1020
  }
1021
+ if (named === 'opengateway') {
1022
+ if (!credential.openGatewayKey) return { refuse: 'pod: Pod 에 opengateway 키 없음 — 호스트 키 캐시 또는 OPENGATEWAY_API_KEY 필요' };
1023
+ if (!childModel?.trim()) return { refuse: 'pod: opengateway 자식 모델 없음 — --child-llm-model 필요' };
1024
+ if (!isVerifiedOpenGatewayModel(childModel.trim())) return { refuse: `pod: opengateway model not verified: ${childModel.trim()}` };
1025
+ return { provider: 'opengateway' };
1026
+ }
981
1027
  // BEDROCK-PROVIDER ② — 자격은 클러스터의 이름 있는 Secret 에서 온다. 있나·키가 갖춰졌나는 Pod 발사 «전»(apply 0)에 판정한다(checkBedrockPodSecret).
982
1028
  if (named === 'bedrock') return { provider: 'bedrock' };
983
1029
  // ZAI-POD-CHILD — glm 도 같은 결: 자격은 클러스터의 이름 있는 ZAI Secret · 있나·동시 상한은 Pod 발사 «전»(apply 0)에 판정한다(pod-zai-child.ts).
@@ -1012,7 +1058,8 @@ export function podChildLlmArgs(options: Pick<PodSpawnOptions, 'provider' | 'chi
1012
1058
  const provider = options.provider === 'openai-codex' && !options.childProviderExplicit ? undefined : options.provider;
1013
1059
  if (!provider) return [];
1014
1060
  const model = options.childModel?.trim() || (provider === 'grok' ? defaultGrokModel().id : provider === 'glm' ? ZAI_CODING_DEFAULT_MODEL : undefined);
1015
- if (provider === 'openrouter' && !model) throw new Error('pod: openrouter 자식 모델 없음 — --child-llm-model 필요');
1061
+ if ((provider === 'openrouter' || provider === 'opengateway') && !model) throw new Error(`pod: ${provider} 자식 모델 없음 — --child-llm-model 필요`);
1062
+ if (provider === 'opengateway' && !isVerifiedOpenGatewayModel(model!)) throw new Error(`pod: opengateway model not verified: ${model}`);
1016
1063
  // `self implement` registers no --child-llm-effort — passing it kills the Pod at argument parsing, so effort stays host-only.
1017
1064
  return ['--child-llm-provider', provider, ...(model ? ['--child-llm-model', model] : [])];
1018
1065
  }
@@ -1078,6 +1125,8 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
1078
1125
  const childModelIndex = o.args.indexOf('--child-llm-model');
1079
1126
  const openrouterModel = childProviderIndex >= 0 && o.args[childProviderIndex + 1] === 'openrouter' && childModelIndex >= 0
1080
1127
  ? o.args[childModelIndex + 1] : undefined;
1128
+ const openGatewayModel = childProviderIndex >= 0 && o.args[childProviderIndex + 1] === 'opengateway' && childModelIndex >= 0
1129
+ ? o.args[childModelIndex + 1] : undefined;
1081
1130
  // POD-ANTHROPIC-PROVIDER — anthropic 자식은 키(env · secretKeyRef)만 쓴다. codex·grok 자격 파일을 깔지 않는다.
1082
1131
  const anthropicChild = childProviderIndex >= 0 && o.args[childProviderIndex + 1] === 'anthropic';
1083
1132
  const goalPath = o.goalDoc ? `'${(o.goalDoc.startsWith('-') ? `./${o.goalDoc}` : o.goalDoc).replace(/'/g, `'\\''`)}'` : undefined;
@@ -1101,7 +1150,9 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
1101
1150
  'mkdir -p "$HOME/.elanous" && install -m 600 /creds/llm-fallback-config.json "$HOME/.elanous/config.json" || exit 6',
1102
1151
  `echo "ELANOUS_POD_FALLBACK_CHAIN source=${o.fallbackChain.source} steps=${o.fallbackChain.steps.join(',')}"`,
1103
1152
  ] : []),
1104
- openrouterModel
1153
+ openGatewayModel
1154
+ ? 'mkdir -p ~/.elanous && export ELANOUS_LLM_PROVIDER=opengateway'
1155
+ : openrouterModel
1105
1156
  ? o.reviewerCodexAuth
1106
1157
  ? 'mkdir -p ~/.elanous ~/.codex && cp /creds/elanous-auth.json ~/.elanous/auth.json && cp /creds/codex-auth.json ~/.codex/auth.json && chmod 600 ~/.elanous/auth.json ~/.codex/auth.json && export ELANOUS_LLM_PROVIDER=openrouter'
1107
1158
  : 'mkdir -p ~/.elanous && export ELANOUS_LLM_PROVIDER=openrouter'
@@ -1120,7 +1171,7 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
1120
1171
  // 🔑 스킬 키(.env) — 이미지엔 없다. 이 런의 Secret 에서 각 스킬 폴더로 0600 복사(값은 로그에 안 나온다).
1121
1172
  ...(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(',')}"`] : []),
1122
1173
  'git config --global user.name "elanous pod child" && git config --global user.email "noreply@anthropic.com" && gh auth setup-git',
1123
- podGitPushGuardScript(),
1174
+ podGitPushGuardScript(o.runId),
1124
1175
  'curl -s -m 3 -o /dev/null http://host.orb.internal:31415/health && { echo "[pod] ISOLATION FAIL"; exit 3; }',
1125
1176
  // SI-POD-STAGE-MARKERS — Pod 시계 단계 표지(`ELANOUS_POD_STAGE <stage> <start|end> <epoch초>`): 호스트가 Job 로그에서 읽어
1126
1177
  // pod.lifecycle stages 의 clone · mirror · run 을 채운다. 실패한 단계는 끝 표지가 없다(→ missing · 지어내지 않는다).
@@ -1291,6 +1342,85 @@ function defaultGhComment(prNumber: number, body: string): void {
1291
1342
  if (r.status !== 0) throw new Error((r.stderr || r.error?.message || `gh exited ${r.status}`).trim().slice(0, 200));
1292
1343
  }
1293
1344
 
1345
+ function defaultLookupOpenPrByBranch(branch: string, repoUrl: string): OpenPrByBranch {
1346
+ const repo = /^https:\/\/github\.com\/([\w.-]+\/[\w.-]+?)(?:\.git)?\/?$/u.exec(repoUrl)?.[1];
1347
+ if (!repo) return { status: 'failed', reason: 'repository-unrecognized' };
1348
+ if (!branch || !/^[A-Za-z0-9._/-]+$/u.test(branch) || branch.includes('..')) return { status: 'failed', reason: 'branch-invalid' };
1349
+ const result = spawnSync('gh', ['pr', 'list', '--repo', repo, '--state', 'open', '--head', branch,
1350
+ '--json', 'number,url,headRefName,isCrossRepository', '--limit', '100'], { encoding: 'utf8', timeout: 10_000, maxBuffer: 1_048_576 });
1351
+ if (result.status !== 0) return { status: 'failed', reason: result.error ? 'gh-list-unavailable' : `gh-list-rc-${result.status ?? 'null'}` };
1352
+ let rows: unknown;
1353
+ try { rows = JSON.parse(result.stdout); } catch { return { status: 'failed', reason: 'gh-json-invalid' }; }
1354
+ if (!Array.isArray(rows)) return { status: 'failed', reason: 'gh-json-invalid' };
1355
+ if (rows.some((row) => row === null || typeof row !== 'object'
1356
+ || typeof (row as { number?: unknown }).number !== 'number'
1357
+ || !Number.isSafeInteger((row as { number: number }).number)
1358
+ || typeof (row as { url?: unknown }).url !== 'string'
1359
+ || typeof (row as { headRefName?: unknown }).headRefName !== 'string'
1360
+ || typeof (row as { isCrossRepository?: unknown }).isCrossRepository !== 'boolean'))
1361
+ return { status: 'failed', reason: 'gh-json-invalid' };
1362
+ if (rows.length >= 100) return { status: 'failed', reason: 'gh-result-limit-reached' };
1363
+ const matching = rows.filter((row): row is { number: number; url: string; headRefName: string; isCrossRepository: false } => {
1364
+ if (row === null || typeof row !== 'object') return false;
1365
+ const candidate = row as { number?: unknown; url?: unknown; headRefName?: unknown; isCrossRepository?: unknown };
1366
+ return candidate.headRefName === branch && candidate.isCrossRepository === false && typeof candidate.number === 'number'
1367
+ && Number.isSafeInteger(candidate.number) && candidate.number > 0 && typeof candidate.url === 'string';
1368
+ });
1369
+ if (matching.length === 0) return { status: 'not-found', reason: rows.some((row) =>
1370
+ (row as { headRefName: string }).headRefName === branch && (row as { isCrossRepository: boolean }).isCrossRepository)
1371
+ ? 'cross-repository-only' : 'no-open-pr-for-branch' };
1372
+ if (matching.length !== 1) return { status: 'failed', reason: 'ambiguous-open-pr-for-branch' };
1373
+ const row = matching[0]!;
1374
+ if (row.url.replace(/\/$/u, '').toLowerCase() !== `https://github.com/${repo}/pull/${row.number}`.toLowerCase())
1375
+ return { status: 'failed', reason: 'pr-url-mismatch' };
1376
+ return { status: 'found', prNumber: row.number, prUrl: row.url };
1377
+ }
1378
+
1379
+ type PodImageObservation = { podName: string | null; podUid: string | null; podCreatedAt: string | null;
1380
+ imageID: string | null; digest: string | null; digestUnavailable: string | null };
1381
+
1382
+ /** Read the newest Pod owned by this Job. A stale replacement must never be named as the final Pod. */
1383
+ function readJobPodImage(kubectl: Kubectl, namespace: string, job: string, jobUid?: string): PodImageObservation {
1384
+ const unavailable = (reason: string, podName: string | null = null, imageID: string | null = null,
1385
+ podUid: string | null = null, podCreatedAt: string | null = null): PodImageObservation =>
1386
+ ({ podName, podUid, podCreatedAt, imageID, digest: null, digestUnavailable: reason });
1387
+ let result: ReturnType<Kubectl>;
1388
+ try { result = kubectl(['-n', namespace, 'get', 'pods', '-l', `job-name=${job}`, '-o', 'json', '--request-timeout=5s']); }
1389
+ catch { return unavailable('pod-status-threw'); }
1390
+ if (result.status !== 0) return unavailable(`pod-status-rc-${result.status ?? 'null'}`);
1391
+ let rows: unknown;
1392
+ try { rows = (JSON.parse(result.stdout) as { items?: unknown }).items; }
1393
+ catch { return unavailable('pod-status-invalid-json'); }
1394
+ if (!Array.isArray(rows)) return unavailable('pod-status-invalid-items');
1395
+ const owned = rows.filter((row): row is {
1396
+ metadata: { name: string; uid?: string; creationTimestamp?: string; ownerReferences: Array<{ kind: string; name: string; uid?: string }> };
1397
+ status?: { containerStatuses?: Array<{ name?: string; imageID?: string }> };
1398
+ } => {
1399
+ if (!row || typeof row !== 'object') return false;
1400
+ const meta = (row as { metadata?: unknown }).metadata;
1401
+ if (!meta || typeof meta !== 'object') return false;
1402
+ const m = meta as { name?: unknown; ownerReferences?: unknown };
1403
+ if (typeof m.name !== 'string' || !Array.isArray(m.ownerReferences)) return false;
1404
+ return m.ownerReferences.some((owner: unknown) => !!owner && typeof owner === 'object'
1405
+ && (owner as { kind?: unknown }).kind === 'Job' && (owner as { name?: unknown }).name === job
1406
+ && (!jobUid || (owner as { uid?: unknown }).uid === jobUid));
1407
+ }).sort((a, b) => {
1408
+ const at = Date.parse(a.metadata.creationTimestamp ?? '') || 0;
1409
+ const bt = Date.parse(b.metadata.creationTimestamp ?? '') || 0;
1410
+ return bt - at || b.metadata.name.localeCompare(a.metadata.name);
1411
+ });
1412
+ const pod = owned[0];
1413
+ if (!pod) return unavailable(rows.length ? 'no-job-owned-pod' : 'pod-not-created');
1414
+ const podUid = typeof pod.metadata.uid === 'string' ? pod.metadata.uid : null;
1415
+ const podCreatedAt = typeof pod.metadata.creationTimestamp === 'string' ? pod.metadata.creationTimestamp : null;
1416
+ const child = pod.status?.containerStatuses?.find((entry) => entry.name === 'child');
1417
+ if (!child) return unavailable('child-container-not-started', pod.metadata.name, null, podUid, podCreatedAt);
1418
+ const imageID = typeof child.imageID === 'string' ? child.imageID : null;
1419
+ if (!imageID?.trim()) return unavailable('image-id-missing', pod.metadata.name, imageID, podUid, podCreatedAt);
1420
+ const digest = /(?:^|[@/])(sha256:[0-9a-f]{64})$/iu.exec(imageID.trim())?.[1]?.toLowerCase() ?? null;
1421
+ return { podName: pod.metadata.name, podUid, podCreatedAt, imageID, digest, digestUnavailable: digest ? null : 'nonstandard-image-id' };
1422
+ }
1423
+
1294
1424
  export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplementJobSpawn {
1295
1425
  if (options.repairCommitOnly && (options.source?.kind !== 'pr' || options.source.number !== options.repairCommitOnly.pr || !/^[0-9a-f]{40}$/.test(options.repairCommitOnly.baseHead))) {
1296
1426
  throw new Error('repair commit-only requires a matching PR source and exact head');
@@ -1599,12 +1729,13 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1599
1729
  const jobArgs = zaiRoute === 'openrouter' ? zaiOverflowChildArgs(args, ZAI_OPENROUTER_FALLBACK_MODEL) : args;
1600
1730
  const grok = options.provider === 'grok';
1601
1731
  const openrouter = options.provider === 'openrouter' || zaiRoute === 'openrouter';
1732
+ const openGateway = options.provider === 'opengateway';
1602
1733
  const anthropic = options.provider === 'anthropic';
1603
1734
  const bedrockChild = options.provider === 'bedrock';
1604
1735
  const zaiChild = zaiRoute === 'zai';
1605
1736
  /** API 키 자식 — codex 계정·자격 파일을 안 쓴다(키는 작업별 creds Secret 의 `env-<KEY>` · bedrock·glm 은 이름 있는 Secret 으로만). */
1606
- const apiKeyChild = openrouter || anthropic || bedrockChild || zaiChild;
1607
- const plannedAccount = retryAccount ?? (grok ? 'grok' : openrouter ? 'openrouter' : anthropic ? 'anthropic' : bedrockChild ? 'bedrock' : zaiChild ? 'glm' : options.accountBroker?.() ?? options.account ?? 'team');
1737
+ const apiKeyChild = openrouter || openGateway || anthropic || bedrockChild || zaiChild;
1738
+ const plannedAccount = retryAccount ?? (grok ? 'grok' : openrouter ? 'openrouter' : openGateway ? 'opengateway' : anthropic ? 'anthropic' : bedrockChild ? 'bedrock' : zaiChild ? 'glm' : options.accountBroker?.() ?? options.account ?? 'team');
1608
1739
  debug.log('self-implement.pod', 'account', { spaceId: input.spaceId, account: plannedAccount, brokered: Boolean(options.accountBroker) });
1609
1740
  const plannedCodexAccounts = grok || apiKeyChild || !options.accountBroker || !options.rotationAccounts ? undefined : [plannedAccount, ...options.rotationAccounts.filter((candidate) => candidate !== plannedAccount && candidate !== failedAccount)];
1610
1741
  if (plannedCodexAccounts && !options.rotationAccounts!.includes(plannedAccount)) throw new Error(`pod: 배분 계정 ${plannedAccount} 이 회전 계획에 없다`);
@@ -1647,6 +1778,17 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1647
1778
  }) : undefined;
1648
1779
  const openrouterKey = openrouter ? env.OPENROUTER_API_KEY?.trim() || (options.readKeyCache ?? defaultReadKeyCache)('OPENROUTER_API_KEY')?.trim() : undefined;
1649
1780
  if (openrouter && !openrouterKey) throw new Error('pod: Pod 에 openrouter 키 없음 — 호스트 키 캐시 또는 OPENROUTER_API_KEY 필요');
1781
+ const openGatewayEnvKey = openGateway ? env.OPENGATEWAY_API_KEY?.trim() : undefined;
1782
+ const openGatewayCachedKey = openGateway ? (options.readKeyCache ?? defaultReadKeyCache)('OPENGATEWAY_API_KEY')?.trim() : undefined;
1783
+ if (openGatewayEnvKey && openGatewayCachedKey && openGatewayEnvKey !== openGatewayCachedKey)
1784
+ throw new Error('pod: opengateway env와 키 캐시가 충돌 — Pod 발사 거부');
1785
+ const openGatewayHostConfig = openGateway ? (options.openGatewayConfig ?? getUserConfig)() : undefined;
1786
+ const openGatewayConfiguredKeys = openGatewayHostConfig ? configuredOpenGatewayKeys(openGatewayHostConfig.llm) : [];
1787
+ if (openGatewayConfiguredKeys.length > 1 || openGatewayConfiguredKeys.some((key) =>
1788
+ (openGatewayEnvKey && openGatewayEnvKey !== key) || (openGatewayCachedKey && openGatewayCachedKey !== key)))
1789
+ throw new Error('pod: opengateway 호스트 설정과 env/키 캐시가 충돌 — Pod 발사 거부');
1790
+ const openGatewayKey = openGatewayEnvKey || openGatewayCachedKey || openGatewayConfiguredKeys[0];
1791
+ if (openGateway && !openGatewayKey) throw new Error('pod: Pod 에 opengateway 키 없음 — 호스트 키 캐시 또는 OPENGATEWAY_API_KEY 필요');
1650
1792
  // POD-ANTHROPIC-PROVIDER — 제품 자격 해석(`getAnthropicApiKey` · 키 캐시 → env)으로 읽는다. 못 읽으면 Pod 를 안 띄운다(fail-closed).
1651
1793
  // ⛔ 값은 로그·오류 문면에 안 싣는다 — «있었다» 만.
1652
1794
  const anthropicKey = anthropic
@@ -1717,7 +1859,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1717
1859
  }
1718
1860
  }
1719
1861
  // Grok 자격은 전용 Secret 키 하나로만 보낸다 — passEnv 의 API 키 중복 전달은 막는다.
1720
- const passKeys = [...new Set([...(options.passEnv ?? []), ...(openrouter ? ['OPENROUTER_API_KEY'] : []), ...(anthropic ? ['ANTHROPIC_API_KEY'] : [])])].filter((key) => key !== 'ELANOUS_LIVE_DETAIL_UNTIL'
1862
+ const passKeys = [...new Set([...(options.passEnv ?? []), ...(openrouter ? ['OPENROUTER_API_KEY'] : []), ...(openGateway ? ['OPENGATEWAY_API_KEY'] : []), ...(anthropic ? ['ANTHROPIC_API_KEY'] : [])])].filter((key) => key !== 'ELANOUS_LIVE_DETAIL_UNTIL'
1721
1863
  && key !== POD_GITHUB_CREDENTIAL_TOKEN_ENV && key !== POD_GITHUB_CREDENTIAL_URL_ENV
1722
1864
  && (!grok || !['XAI_API_KEY', 'GROK_API_KEY', 'GROK_CODE_XAI_API_KEY'].includes(key)));
1723
1865
  const skillEnvs: Record<string, string> = options.skillEnv
@@ -1863,7 +2005,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1863
2005
  authorChoice = { ...authorChoice, model: childModel, modelSource: 'fallback-child' };
1864
2006
  } else debug.log('self-implement.pod', 'role-llm-armed', { job: name, role: 'planning', model: authorChoice.model, source: authorChoice.modelSource, account: roleCodexAccount });
1865
2007
  }
1866
- const hostKey = (k: string): string | undefined => k === 'OPENROUTER_API_KEY' && openrouter ? openrouterKey : k === 'ANTHROPIC_API_KEY' && anthropic ? anthropicKey : env[k] ?? (options.readKeyCache ?? defaultReadKeyCache)(k);
2008
+ const hostKey = (k: string): string | undefined => k === 'OPENROUTER_API_KEY' && openrouter ? openrouterKey : k === 'OPENGATEWAY_API_KEY' && openGateway ? openGatewayKey : k === 'ANTHROPIC_API_KEY' && anthropic ? anthropicKey : env[k] ?? (options.readKeyCache ?? defaultReadKeyCache)(k);
1867
2009
  const hostFallbackNames = (options.fallbackConfig ?? getUserConfig)().llm.fallbackChain;
1868
2010
  const normalizedFallback = normalizeFallbackChain(hostFallbackNames);
1869
2011
  let fallbackSteps = normalizedFallback.chain;
@@ -1932,6 +2074,10 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
1932
2074
  // 🩸 종전엔 여기서 delete 부터 해서, 재개가 원격에서 멀쩡히 돌던 Job 을 죽였다. 실패로 끝난 Job 만 지우고 다시 만든다.
1933
2075
  // ☸️ 노드 레지스트리에 이 판이 있으면 그것을 pull 한다(델타 · 대표 2026-09-26) — 라벨 판정은 로컬 이미지 이름으로 한다.
1934
2076
  const jobImage = member?.imageRef ?? image;
2077
+ // A pool member may pull a different image than the host's local one.
2078
+ // The local Docker label cannot prove that member image's commit.
2079
+ const localImageCommit = options.imageCommit !== undefined ? options.imageCommit : options.kubectl ? null : podImageFreshness({ image }).imageCommit;
2080
+ const jobImageCommit = jobImage === image ? localImageCommit : null;
1935
2081
  const launchRunId = retryAccount || oomRetried || ghLoginRetried ? mintRunId() : childRunId;
1936
2082
  let liveChildRunId = launchRunId;
1937
2083
  // TA-POD-WARMBOOT-PARITY — the id the followed Job prints in its result marker (null = wait on the condition only).
@@ -2167,7 +2313,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2167
2313
  debug.log('self-implement.pod', fit.fitted ? 'grok-deadline-fitted' : 'grok-deadline-kept', { job: name, requestedSeconds: jobDeadlineSeconds, deadlineSeconds: fit.deadlineSeconds, availableSeconds: fit.availableSeconds, relay: Boolean(credentialRelay), marginMs: POD_GROK_EXPIRY_MARGIN_MS });
2168
2314
  jobDeadlineSeconds = fit.deadlineSeconds;
2169
2315
  }
2170
- const job = podJobManifest({ name, namespace, fallbackChain, ...(resultMarkerLine ? { resultMarkerLine } : {}), image: jobImage, workIdentity, ...(env.ELANOUS_TASK_BOUND_PR && /^[1-9]\d*$/.test(env.ELANOUS_TASK_BOUND_PR) && Number.isSafeInteger(Number(env.ELANOUS_TASK_BOUND_PR)) ? { boundPrNumber: Number(env.ELANOUS_TASK_BOUND_PR) } : {}), ...(member?.imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}), repoUrl, ...(options.source ? { source: options.source } : {}), ...(options.repairCommitOnly ? { repairCommitOnly: options.repairCommitOnly } : {}), ...(hostMirror ? { hostMirror } : {}), args: jobArgs, passEnv: jobPassEnv, deadlineSeconds: jobDeadlineSeconds, ...(goalDoc ? { goalDoc } : env.ELANOUS_POD_AUTHOR_ON_POD === '1' && !shard ? { authorSentence: true, ...(env.ELANOUS_POD_AUTHOR_GRADE === 'lite' || env.ELANOUS_POD_AUTHOR_GRADE === 'full' ? { authorGrade: env.ELANOUS_POD_AUTHOR_GRADE } : {}) } : {}), runId: launchRunId, ...(parentRunId ? { parentRunId } : {}), ...(jobArmEnv ? { armEnv: jobArmEnv } : {}), hostId: resolveHostId(env), skillEnvs: Object.keys(skillEnvs), memoryLimit, memoryRequest, imageCommit: 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, ...(roleCodexCredential ? { reviewerCodexAuth: true } : {}), ...(bedrockSpec ? { bedrock: bedrockSpec } : {}), ...(zaiSpec ? { zai: zaiSpec } : {}), ...(zaiFallbackSpec ? { zaiFallback: zaiFallbackSpec } : {}) }); // kubectl 주입(=시험)이면 docker 를 부르지 않는다
2316
+ const job = podJobManifest({ name, namespace, fallbackChain, ...(resultMarkerLine ? { resultMarkerLine } : {}), image: jobImage, workIdentity, ...(env.ELANOUS_TASK_BOUND_PR && /^[1-9]\d*$/.test(env.ELANOUS_TASK_BOUND_PR) && Number.isSafeInteger(Number(env.ELANOUS_TASK_BOUND_PR)) ? { boundPrNumber: Number(env.ELANOUS_TASK_BOUND_PR) } : {}), ...(member?.imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}), repoUrl, ...(options.source ? { source: options.source } : {}), ...(options.repairCommitOnly ? { repairCommitOnly: options.repairCommitOnly } : {}), ...(hostMirror ? { hostMirror } : {}), args: jobArgs, passEnv: jobPassEnv, deadlineSeconds: jobDeadlineSeconds, ...(goalDoc ? { goalDoc } : env.ELANOUS_POD_AUTHOR_ON_POD === '1' && !shard ? { authorSentence: true, ...(env.ELANOUS_POD_AUTHOR_GRADE === 'lite' || env.ELANOUS_POD_AUTHOR_GRADE === 'full' ? { authorGrade: env.ELANOUS_POD_AUTHOR_GRADE } : {}) } : {}), runId: launchRunId, ...(parentRunId ? { parentRunId } : {}), ...(jobArmEnv ? { armEnv: jobArmEnv } : {}), hostId: resolveHostId(env), skillEnvs: Object.keys(skillEnvs), memoryLimit, memoryRequest, imageCommit: jobImageCommit, ...(grok ? { grokCredential: 'grokAuth' in creds && creds.grokAuth ? 'subscription' as const : 'api_key' as const } : {}), ...(codexAccounts ? { codexAccounts } : {}), ...(hostRefresh ? { appCredential: true } : {}), execution: { key: executionKey, attempt: oomRetried ? 2 : 1 }, hostLeaseAdmitted: !!releaseAdmission, ...(roleCodexCredential ? { reviewerCodexAuth: true } : {}), ...(bedrockSpec ? { bedrock: bedrockSpec } : {}), ...(zaiSpec ? { zai: zaiSpec } : {}), ...(zaiFallbackSpec ? { zaiFallback: zaiFallbackSpec } : {}) }); // kubectl 주입(=시험)이면 docker 를 부르지 않는다
2171
2317
  const carriedBranch = goalDoc ? 'goal-doc' as const : env.ELANOUS_POD_AUTHOR_ON_POD === '1' && !shard ? 'author' as const : undefined;
2172
2318
  const carriedChildLlm = carriedBranch ? podDelegatedChildLlm(jobArgs) : undefined;
2173
2319
  jobApplyStartedAt = Date.now();
@@ -2175,6 +2321,10 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2175
2321
  zaiPendingApply.delete(name); // apply 뒤엔 Job 라벨이 센다(성공) · 실패면 셀 것이 없다
2176
2322
  if (a.status !== 0) { cleanupSecret(); return { exitCode: 1, output: a.stderr, error: { code: 'pod-apply', message: a.stderr.trim() } }; }
2177
2323
  releaseAdmission?.applied?.(name, context, namespace);
2324
+ const imageFacts = { job: name, childRunId: liveChildRunId, image: jobImage, imageCommit: jobImageCommit, context };
2325
+ try { appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-job-image', data: imageFacts }, ledgerDir); }
2326
+ catch (error) { debug.log('self-implement.pod', 'job-image-ledger-unavailable', { job: name, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
2327
+ debug.log('self-implement.pod', 'job-image', imageFacts);
2178
2328
  // The injected observer does not acquire a production lease. The gate consumer
2179
2329
  // must compare the Job result and explicitly finalize before enabling replay.
2180
2330
  if (!oomRetried && input.warmWorkerCommand && options.warmPodRequest) {
@@ -2241,7 +2391,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2241
2391
  }
2242
2392
  writePodFragment({ spaceId: input.spaceId, context, namespace, job: name, inboxDir: POD_CONTROL_INBOX_DIR, ...(liveChildRunId === normalizeRunId(liveChildRunId) ? { runId: liveChildRunId } : {}), ...(parentRunId && normalizeRunId(parentRunId) === parentRunId ? { parentRunId } : {}) }, env);
2243
2393
  recorded = true;
2244
- 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 } : {}) });
2394
+ debug.log('self-implement.pod', 'job-applied', { job: name, namespace, ...(member ? { context: member.context } : {}), image: jobImage, spaceId: input.spaceId, passEnv, ...(openrouter ? { openrouterKeyPresent: passEnv.includes('OPENROUTER_API_KEY') } : {}), ...(openGateway ? { openGatewayKeyPresent: passEnv.includes('OPENGATEWAY_API_KEY') } : {}), ...(anthropic ? { anthropicKeyPresent: passEnv.includes('ANTHROPIC_API_KEY') } : {}), ...(bedrockSpec ? { bedrockSecret: bedrockSpec.secretName, bedrockSecretKeys: bedrockSpec.keys, bedrockRegionFrom: bedrockSpec.region.from } : {}), ...(zaiRoute ? { zaiRoute, ...(zaiSpec ? { zaiSecret: zaiSpec.secretName } : {}) } : {}), extraArgs: options.extraArgs ?? [], ...(options.armEnv?.ELANOUS_ARM_ID ? { armId: options.armEnv.ELANOUS_ARM_ID } : {}) });
2245
2395
  let state: 'complete' | 'failed' | 'aborted' = 'failed';
2246
2396
  let failedReason = '';
2247
2397
  let containerReason: string | null = null;
@@ -2253,11 +2403,34 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2253
2403
  // ⭐ METER-POD-ROLLUP-GAP-3 — 런 «도중» 마지막 PARTIAL 줄을 호스트가 쥔다: Pod 가 지워지면(SIGTERM 등) 종료 로그에서 PARTIAL 이 사라진다.
2254
2404
  let livePartialLine: string | null = null;
2255
2405
  let pollsSinceCapture = 0;
2406
+ let imageSnapshotRecorded = false;
2407
+ let terminalImageRecorded = false;
2408
+ const recordPodImage = (final: boolean): void => {
2409
+ if (final ? terminalImageRecorded : imageSnapshotRecorded) return;
2410
+ const observed = readJobPodImage(kubectl, namespace, name, lifecycleJobUid);
2411
+ if (!final && !observed.podName) return;
2412
+ const append = (isFinal: boolean): void => {
2413
+ const data = { job: name, childRunId: liveChildRunId, attempt: oomRetried ? 2 : 1,
2414
+ image: jobImage, imageCommit: jobImageCommit, context, ...observed, final: isFinal };
2415
+ try {
2416
+ appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-image-digest', data }, ledgerDir);
2417
+ if (isFinal) terminalImageRecorded = true;
2418
+ else imageSnapshotRecorded = true;
2419
+ } catch (error) {
2420
+ debug.log('self-implement.pod', 'pod-image-digest-ledger-unavailable', {
2421
+ job: name, final: isFinal, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' });
2422
+ }
2423
+ debug.log('self-implement.pod', 'pod-image-digest', data);
2424
+ };
2425
+ if (final && !imageSnapshotRecorded && observed.podName) append(false);
2426
+ append(final);
2427
+ };
2256
2428
  const capturePolls = Math.max(1, options.usagePartialCapturePolls ?? Math.round(POD_USAGE_PARTIAL_CAPTURE_MS / Math.max(1, options.pollMs ?? 15_000)));
2257
2429
  const markerWait = markerToken ? await import('./pod-result-marker-wait.js') : null;
2258
2430
  const waitStartedAt = Date.now();
2259
2431
  for (;;) {
2260
2432
  if (input.signal?.aborted) {
2433
+ recordPodImage(true);
2261
2434
  // METER-POD-ROLLUP-GAP — Job 삭제 «전»에 로그를 한 번 거둔다: PARTIAL 줄이 삭제와 함께 사라지면 사용량이 0 처럼 보인다.
2262
2435
  try {
2263
2436
  const captured = kubectl(['-n', namespace, 'logs', `job/${name}`, '-c', 'child', '--tail=400']);
@@ -2267,6 +2440,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2267
2440
  state = 'aborted';
2268
2441
  break;
2269
2442
  }
2443
+ if (!imageSnapshotRecorded) recordPodImage(false);
2270
2444
  const g = kubectl(['-n', namespace, 'get', 'job', name, '-o', 'jsonpath={.status.conditions[*].type}']);
2271
2445
  const types = g.stdout;
2272
2446
  if (/Complete|SuccessCriteriaMet/.test(types)) { releaseAdmission?.observed?.(); state = 'complete'; break; }
@@ -2342,6 +2516,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2342
2516
  }
2343
2517
  await sleep(options.pollMs ?? 15_000);
2344
2518
  }
2519
+ if (!terminalImageRecorded) recordPodImage(true);
2345
2520
  let logs = '';
2346
2521
  let logTailReason: string | undefined;
2347
2522
  try {
@@ -2521,14 +2696,52 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2521
2696
  }
2522
2697
  const childFailure = state === 'failed' && !oomKilled && failedReason !== 'DeadlineExceeded'
2523
2698
  ? lastPodChildFailure(logs) : null;
2699
+ let childPrFailure: ReturnType<typeof podPrOpenFailure> = null;
2700
+ if (childFailure) {
2701
+ try {
2702
+ const entries = loadRunLedger(liveChildRunId, ledgerDir);
2703
+ const entry = entries?.slice().reverse().find((row) => row.event === 'pr-open-failed');
2704
+ childPrFailure = podPrOpenFailure(entry?.data, childFailure.rawError);
2705
+ } catch { /* child ledger is optional; terminal result remains available */ }
2706
+ childPrFailure ??= podPrOpenFailure(null, childFailure.rawError);
2707
+ }
2708
+ if (childPrFailure && disposition) {
2709
+ disposition.childEvidence = {
2710
+ ...(disposition.childEvidence ?? {}),
2711
+ prFailure: {
2712
+ stage: childPrFailure.prFailureStage,
2713
+ source: childPrFailure.prFailureSource,
2714
+ errorTail: childPrFailure.childErrorTail,
2715
+ ...(childPrFailure.blockingPrNumber ? { blockingPrNumber: childPrFailure.blockingPrNumber } : {}),
2716
+ ...(childPrFailure.candidateCardId ? { candidateCardId: childPrFailure.candidateCardId } : {}),
2717
+ ...(childPrFailure.blockingCardId ? { blockingCardId: childPrFailure.blockingCardId } : {}),
2718
+ },
2719
+ };
2720
+ }
2524
2721
  let noResultDiagnostic: string | null = null;
2525
- // POD-NORESULT: the Job runs with backoffLimit 0, so any non-zero child exit is «BackoffLimitExceeded» — not a reason to skip the ledger.
2526
- if (state === 'failed' && containerExitCode !== null && containerExitCode !== 0 && !disposition && !oomKilled && failedReason !== 'DeadlineExceeded') {
2722
+ // POD-NORESULT: a terminal Job without a parsed disposition is incomplete even when the Job condition says Complete.
2723
+ const noResultCandidate = !disposition && !oomKilled && failedReason !== 'DeadlineExceeded' &&
2724
+ (state === 'complete' || (state === 'failed' && containerExitCode !== 0));
2725
+ if (noResultCandidate) {
2527
2726
  try { noResultDiagnostic = podNoResultDiagnostic(readFileSync(runLedgerPath(liveChildRunId, ledgerDir), 'utf8')); }
2528
2727
  catch { /* ledger not returned: do not invent a stage */ }
2529
2728
  noResultDiagnostic ??= 'child terminal result missing; last ledger stage=unknown; round=unknown; mustFix=unknown';
2530
2729
  }
2531
- if (childFailure) debug.log('self-implement.pod', 'child-error', { job: name, stage: childFailure.stage, error: childFailure.error }, { compact: { stringMax: 500 } });
2730
+ // POD-NORESULT-LOG-TAIL: the Job runs with backoffLimit 0, so the failed Pod — and its container log —
2731
+ // is collected right after this. When the child ledger cannot even name the stage, read the tail here,
2732
+ // before deletion, so the parent keeps a maskable reason instead of `childEvidence=null`.
2733
+ let noResultLogEvidence: Record<string, unknown> | null = null;
2734
+ if (noResultDiagnostic?.includes('stage=unknown')) {
2735
+ const logRead = readPodNoResultLogTail({ run: kubectl, namespace, job: name, maxLines: POD_NO_RESULT_LOG_TAIL_LINES });
2736
+ if ('tail' in logRead) {
2737
+ noResultLogEvidence = { stderr: logRead.tail.join('\n'), logTail: logRead.tail, logTailLines: logRead.tail.length };
2738
+ debug.log('self-implement.pod', 'no-result-log-tail', { job: name, childRunId: liveChildRunId, diagnostic: noResultDiagnostic, lines: logRead.tail.length, logTail: logRead.tail }, { compact: { stringMax: 500 } });
2739
+ } else {
2740
+ noResultLogEvidence = { stderr: `log-unavailable: ${logRead.unavailable}` };
2741
+ debug.log('self-implement.pod', 'no-result-log-tail', { job: name, childRunId: liveChildRunId, diagnostic: noResultDiagnostic, logUnavailable: logRead.unavailable }, { level: 'warn' });
2742
+ }
2743
+ }
2744
+ if (childFailure) debug.log('self-implement.pod', 'child-error', { job: name, stage: childFailure.stage, error: childFailure.error, ...(childPrFailure ?? {}) }, { compact: { stringMax: 500 } });
2532
2745
  if (noResultDiagnostic && !noResultDiagnostic.includes('stage=unknown')) {
2533
2746
  debug.log('self-implement.pod', 'no-result-ledger-diagnostic', { job: name, childRunId: liveChildRunId, diagnostic: noResultDiagnostic }, { compact: { stringMax: 500 } });
2534
2747
  }
@@ -2536,8 +2749,9 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2536
2749
  ...(state === 'failed' && !oomKilled && failedReason !== 'DeadlineExceeded'
2537
2750
  ? { ...(childFailure ? { childStage: childFailure.stage } : disposition?.stage ? { childStage: disposition.stage } : {}),
2538
2751
  // POD-NORESULT: a parsed result row without error text is not a missing result line (UX 10-07 01:01: 28 of 28 exit-2 Jobs printed one).
2539
- childError: childFailure?.error ?? (disposition?.stage ? 'result-without-error' : 'no-result-line') } : {}) },
2540
- state === 'failed' && !oomKilled && failedReason !== 'DeadlineExceeded' ? { compact: { stringMax: 500 } } : undefined);
2752
+ childError: childFailure?.error ?? (disposition?.stage ? 'result-without-error' : 'no-result-line'),
2753
+ ...(childPrFailure ?? {}) } : {}) },
2754
+ (state === 'failed' && !oomKilled && failedReason !== 'DeadlineExceeded') ? { compact: { stringMax: 500 } } : undefined);
2541
2755
  // POD-LIFECYCLE-STAGES: one `pod.lifecycle stages` line — clone · mirror · run come from the Pod's own
2542
2756
  // `ELANOUS_POD_STAGE` markers (SI-POD-STAGE-MARKERS), read from that Pod's full named log. ⛔ No `--tail=400` fallback:
2543
2757
  // a tail can lose the real `run start` and pair a child-printed look-alike with the real `run end` (review mf) —
@@ -2606,10 +2820,11 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2606
2820
  appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-oom-retry-finished', data: { job: name, executionKey, fromChildRunId: retryFromChildRunId, childRunId: liveChildRunId, state } }, ledgerDir);
2607
2821
  }
2608
2822
  if (goalFile) {
2823
+ const noResultOutcome = state === 'failed' && containerExitCode !== null && noResultDiagnostic ? 'abandoned' : 'pod-no-result';
2609
2824
  const record: GoalExecutionRecord = {
2610
2825
  runId: liveChildRunId,
2611
2826
  stage: (state === 'aborted' || !disposition ? 'aborted' : disposition.stage) as GoalExecutionRecord['stage'],
2612
- outcome: (!disposition ? noResultDiagnostic ? 'abandoned' : 'pod-no-result' : state === 'complete' && disposition.merged ? 'merged' : state === 'complete' && disposition.ok ? 'completed' : 'abandoned') as GoalExecutionRecord['outcome'],
2827
+ outcome: (!disposition ? noResultOutcome : state === 'complete' && disposition.merged ? 'merged' : state === 'complete' && disposition.ok ? 'completed' : 'abandoned') as GoalExecutionRecord['outcome'],
2613
2828
  ok: state === 'complete' && (disposition?.ok ?? false),
2614
2829
  ...(disposition?.prNumber !== undefined ? { prNumber: disposition.prNumber } : {}),
2615
2830
  completedAt: new Date().toISOString(),
@@ -2619,16 +2834,41 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2619
2834
  }
2620
2835
  // The host still has the complete Pod log here. The CLI only retains a short stdout tail,
2621
2836
  // so preserve the parsed child facts in the parent ledger before that tail is returned.
2622
- if (state === 'failed' || state === 'aborted') {
2837
+ if (state === 'failed' || state === 'aborted' || noResultLogEvidence || (state === 'complete' && noResultDiagnostic !== null)) {
2623
2838
  // Every terminal attempt writes a marker, including one with no parseable child facts.
2624
2839
  // The next attempt must never inherit an earlier Pod's classification.
2625
- try { appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-child-evidence', data: { childEvidence: disposition?.childEvidence ?? null, childRunId: liveChildRunId, job: name } }, ledgerDir); }
2840
+ const childEvidence = noResultLogEvidence ?? disposition?.childEvidence ?? null;
2841
+ try { appendRunLedgerEntry({ runId: ledgerRunId, event: 'pod-child-evidence', data: { childEvidence, childRunId: liveChildRunId, job: name } }, ledgerDir); }
2626
2842
  catch (error) { debug.log('self-implement.pod', 'child-evidence-write-failed', { job: name, childRunId: liveChildRunId, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
2627
2843
  }
2628
- const terminalRecord = classifyPodTerminal({ childRunId: liveChildRunId, state, disposition, env,
2629
- prBody: options.draftResidue ? undefined : podTerminalPrBody(disposition),
2630
- goalText: parentGoal ?? input.feature, at: new Date().toISOString() });
2631
- try { await (options.draftResidue ?? applyPodTerminal)(terminalRecord, { ledgerRunId, ledgerDir }); }
2844
+ let prLookup: OpenPrByBranch | undefined;
2845
+ const terminalBranch = typeof disposition?.branch === 'string' ? disposition.branch.trim() : '';
2846
+ if (disposition && (state === 'failed' || state === 'aborted' || disposition.ok === false)
2847
+ && (!Number.isSafeInteger(disposition.prNumber) || (disposition.prNumber ?? 0) <= 0) && terminalBranch) {
2848
+ try { prLookup = await (options.lookupOpenPrByBranch ?? defaultLookupOpenPrByBranch)(terminalBranch, repoUrl); }
2849
+ catch { prLookup = { status: 'failed', reason: 'pr-lookup-threw' }; }
2850
+ }
2851
+ const terminalDisposition = prLookup?.status === 'found' && disposition
2852
+ ? { ...disposition, prNumber: prLookup.prNumber, prUrl: prLookup.prUrl } : disposition;
2853
+ if (prLookup?.status === 'found') debug.log('self-implement.pod', 'pr-terminal-bound-by-branch', {
2854
+ branch: terminalBranch, prNumber: prLookup.prNumber, prUrl: prLookup.prUrl, status: 'found', job: name });
2855
+ else if (prLookup) debug.log('self-implement.pod', 'pr-terminal-branch-lookup', {
2856
+ branch: terminalBranch, status: prLookup.status, reason: prLookup.reason, job: name });
2857
+ const terminalRecord = { ...classifyPodTerminal({ childRunId: liveChildRunId, state, disposition: terminalDisposition, env,
2858
+ prBody: options.draftResidue || prLookup?.status === 'found' ? undefined : podTerminalPrBody(terminalDisposition),
2859
+ goalText: parentGoal ?? input.feature, at: new Date().toISOString() }),
2860
+ ...(prLookup ? { prLookup: prLookup.status === 'found' ? { status: 'found', source: 'branch-lookup' }
2861
+ : { status: prLookup.status, reason: prLookup.reason } } : {}),
2862
+ ...(prLookup?.status === 'found' ? { prNumberSource: 'branch-lookup' } : {}) };
2863
+ try {
2864
+ if (options.draftResidue) await options.draftResidue(terminalRecord, { ledgerRunId, ledgerDir });
2865
+ else if (prLookup?.status === 'found') {
2866
+ // This lookup only repairs the host ledger's PR identity. The draft sweeper owns PR writes.
2867
+ const alreadyRecorded = loadRunLedger(ledgerRunId, ledgerDir)?.some((entry) =>
2868
+ entry.event === 'pr-terminal' && entry.data.childRunId === liveChildRunId);
2869
+ if (!alreadyRecorded) appendRunLedgerEntry({ runId: ledgerRunId, event: 'pr-terminal', data: terminalRecord }, ledgerDir);
2870
+ } else await applyPodTerminal(terminalRecord, { ledgerRunId, ledgerDir });
2871
+ }
2632
2872
  catch (error) { debug.log('draft.residue', 'failed', { childRunId: liveChildRunId, prNumber: terminalRecord.prNumber,
2633
2873
  terminalClass: terminalRecord.terminalClass, owner: terminalRecord.owner, ownerSource: terminalRecord.ownerSource,
2634
2874
  step: 'apply', reason: error instanceof Error ? error.message : String(error) }); }
@@ -2650,7 +2890,10 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2650
2890
  : disposition;
2651
2891
  const podExitCode = state === 'complete' && disposition?.stage !== 'host-regate-failed' && disposition?.ok !== false ? 0 : 1;
2652
2892
  // SCHED-CONTRACT: a failed child must say why outside the Pod, not «no error diagnostic».
2653
- const podReason = podExitCode === 1 ? extractPodFailureReason({ logs, logTailReason, containerReason, jobReason: failedReason || undefined, deadlineSeconds, result: disposition ? { stage: disposition.stage, prUrl: disposition.prUrl ?? null, prNumber: disposition.prNumber ?? null } : null }) : undefined;
2893
+ const rawPodReason = podExitCode === 1 ? extractPodFailureReason({ logs, logTailReason, containerReason, jobReason: failedReason || undefined, deadlineSeconds, result: disposition ? { stage: disposition.stage, prUrl: disposition.prUrl ?? null, prNumber: disposition.prNumber ?? null } : null }) : undefined;
2894
+ const podReason = rawPodReason
2895
+ ? noResultLogEvidence ? boundedPodLogText(rawPodReason, POD_NO_RESULT_LOG_REASON_MAX_CHARS) : rawPodReason
2896
+ : undefined;
2654
2897
  if (podReason) debug.log('self-implement.pod', 'failure-reason', { job: name, reason: podReason });
2655
2898
  if (podExitCode === 1) {
2656
2899
  // HARNESS-FULL-GRAPH — 실패 쪽 두 노드: 결과 해석(result-parse) → 수확(salvage, Pod Job 이 push 한 줄로 판정).
@@ -2668,7 +2911,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
2668
2911
  ? { error: { code: 'pod-gh-login-failed', message: `Job ${name} GitHub 앱 로그인 실패 (container=${containerReason ?? 'unknown'}/7, attempt=${ghLoginRetried ? 2 : 1}) — 새 토큰으로 ${ghLoginRetried ? '1회 재시도했으나 다시 실패' : '재시도 불가'}` } }
2669
2912
  : oomKilled
2670
2913
  ? { error: { code: 'pod-oom-killed', message: `Job ${name} failed (OOMKilled/${containerExitCode ?? 'unknown'}, memoryLimit=${memoryLimit})${oomRetried || (memoryGi(reattachedMemoryLimit ?? memoryLimit) ?? 0) >= (memoryGi(podMemoryLimitFor(input.feature, { ...env, ELANOUS_POD_MEMORY_TIER: 'high' }).limit) ?? Infinity) ? ` — OOM · ${oomRetried ? retryTier : 'high'} 에서도` : ''}\n${formatLastMemSample(samples.at(-1))}` } }
2671
- : { error: { code: 'pod-job-failed', message: `Job ${name} failed${failedReason || containerReason ? ` (${[failedReason, containerReason ? `container=${containerReason}/${containerExitCode ?? 'unknown'}` : ''].filter(Boolean).join(', ')})` : ''}${childFailure ? ` — childStage=${childFailure.stage} · childError=${childFailure.error}` : disposition?.stage ? ` — childStage=${disposition.stage} · reason=${podReason}` : ` — childError=no-result-line · reason=${podReason}`}${noResultDiagnostic ? ` · ${noResultDiagnostic}` : ''}` } }
2914
+ : { error: { code: 'pod-job-failed', message: `Job ${name} failed${failedReason || containerReason ? ` (${[failedReason, containerReason ? `container=${containerReason}/${containerExitCode ?? 'unknown'}` : ''].filter(Boolean).join(', ')})` : ''}${childFailure ? ` — childStage=${childFailure.stage} · childError=${childFailure.error}` : disposition?.stage ? ` — childStage=${disposition.stage} · reason=${podReason}` : ` — childError=no-result-line · reason=${podReason}`}${noResultDiagnostic && containerExitCode !== null ? ` · ${noResultDiagnostic}` : ''}` } }
2672
2915
  : podReason && !childFailure?.error ? { error: { code: 'pod-child-failed', message: podReason } } : {}),
2673
2916
  ...(finishedDisposition ? { disposition: finishedDisposition } : {}),
2674
2917
  };
@@ -2736,7 +2979,43 @@ export function recordPodSalvage(logs: string, job: string, log: (category: stri
2736
2979
  return rows.map((row) => row.branch);
2737
2980
  }
2738
2981
 
2739
- function lastPodChildFailure(logs: string): { stage: string; error: string } | null {
2982
+ type PodPrOpenFailure = {
2983
+ childErrorTail: string;
2984
+ prFailureStage: 'pr-open-failed';
2985
+ prFailureSource: 'child-ledger' | 'terminal-result';
2986
+ blockingPrNumber?: number;
2987
+ candidateCardId?: string;
2988
+ blockingCardId?: string;
2989
+ };
2990
+
2991
+ /** Preserve the actionable tail independently of the bounded human error. Never infer a card from a PR number. */
2992
+ function podPrOpenFailure(ledger: Record<string, unknown> | undefined | null, terminalError: string): PodPrOpenFailure | null {
2993
+ const ledgerError = typeof ledger?.error === 'string' ? ledger.error : '';
2994
+ // A previous rework round can leave a pr-open-failed row behind. Only a terminal PR failure may consume it.
2995
+ if (!/draft PR 생성 실패|PR (?:gh|guard) 실패|same-cell reuse:/u.test(terminalError)) return null;
2996
+ // The terminal must contain the complete ledger error. A shared prefix such as "same-cell"
2997
+ // cannot distinguish an earlier rework round from the current failed attempt.
2998
+ const matchingLedger = ledgerError.length >= 20 && terminalError.includes(ledgerError);
2999
+ const source = terminalError || ledgerError;
3000
+ const sanitized = redactSecretText(source).replace(/\/home\/[^/\s]+\//gu, '~/');
3001
+ const tail = sanitized.split(/\r\n|\n|\r/u).filter(Boolean).at(-1) ?? sanitized;
3002
+ const terminalHasIds = /(?:blockers=|blockingPr=|candidateCardId=)/u.test(terminalError);
3003
+ const detail = `${terminalError}\n${matchingLedger ? ledgerError : ''}`;
3004
+ const blocker = /(?:blockers=|blockingPr=)\s*#?(\d+)(?::([A-Za-z0-9_-]{1,128}))?/u.exec(detail);
3005
+ const prNumber = blocker ? Number(blocker[1]) : null;
3006
+ const candidate = /candidateCardId=([A-Za-z0-9_-]{1,128})/u.exec(detail)?.[1];
3007
+ const card = blocker?.[2];
3008
+ return {
3009
+ childErrorTail: tail.slice(-240),
3010
+ prFailureStage: 'pr-open-failed',
3011
+ prFailureSource: terminalHasIds || !matchingLedger ? 'terminal-result' : 'child-ledger',
3012
+ ...(prNumber && Number.isSafeInteger(prNumber) ? { blockingPrNumber: prNumber } : {}),
3013
+ ...(candidate && candidate !== 'unknown' ? { candidateCardId: candidate } : {}),
3014
+ ...(card && card !== 'unknown' ? { blockingCardId: card } : {}),
3015
+ };
3016
+ }
3017
+
3018
+ function lastPodChildFailure(logs: string): { stage: string; error: string; rawError: string } | null {
2740
3019
  for (const line of logs.split('\n').reverse()) {
2741
3020
  if (!line.trimStart().startsWith('{')) continue;
2742
3021
  let result: unknown;
@@ -2746,14 +3025,67 @@ function lastPodChildFailure(logs: string): { stage: string; error: string } | n
2746
3025
  const terminal = podTerminalRow(row);
2747
3026
  if (!terminal) continue;
2748
3027
  if (terminal.ok !== false || typeof terminal.error !== 'string') return null;
2749
- const lines = terminal.error.replace(/\/home\/[^/\s]+\//g, '~/').split(/\r\n|\n|\r/);
3028
+ const lines = redactSecretText(terminal.error).replace(/\/home\/[^/\s]+\//g, '~/').split(/\r\n|\n|\r/);
2750
3029
  const first = lines[0]!.slice(0, 240);
2751
3030
  const last = lines.at(-1)!.slice(0, 240);
2752
- return { stage: terminal.stage, error: first === last ? first : `${first}\n${last}` };
3031
+ return { stage: terminal.stage, error: first === last ? first : `${first}\n${last}`, rawError: terminal.error };
2753
3032
  }
2754
3033
  return null;
2755
3034
  }
2756
3035
 
3036
+ /** POD-NORESULT-LOG-TAIL: how many container-log lines a failed Pod leaves behind for the parent. */
3037
+ export const POD_NO_RESULT_LOG_TAIL_LINES = 80;
3038
+ const POD_NO_RESULT_LOG_TAIL_LINE_CHARS = 512;
3039
+ const POD_NO_RESULT_LOG_TAIL_MAX_CHARS = 8_192;
3040
+ const POD_NO_RESULT_LOG_REASON_MAX_CHARS = 512;
3041
+
3042
+ function boundedPodLogText(source: string, maxChars: number): string {
3043
+ const redacted = redactSecretText(source).replace(/[\r\n\t]+/g, ' ').replace(/\s+/g, ' ').trim();
3044
+ return redacted.length > maxChars ? `${redacted.slice(0, maxChars)}…[truncated]` : redacted;
3045
+ }
3046
+
3047
+ /** 로그 꼬리 — 마지막 N줄만, 한 줄씩 비밀값 마스킹. */
3048
+ export function podNoResultLogTailLines(source: string, maxLines: number = POD_NO_RESULT_LOG_TAIL_LINES): string[] {
3049
+ const lines = source.replace(/\r\n/g, '\n').split('\n');
3050
+ while (lines.length && lines[lines.length - 1] === '') lines.pop();
3051
+ const result: string[] = [];
3052
+ let chars = 0;
3053
+ for (const line of lines.slice(-Math.max(0, maxLines)).reverse()) {
3054
+ if (chars >= POD_NO_RESULT_LOG_TAIL_MAX_CHARS) break;
3055
+ const bounded = boundedPodLogText(line, Math.min(POD_NO_RESULT_LOG_TAIL_LINE_CHARS, POD_NO_RESULT_LOG_TAIL_MAX_CHARS - chars));
3056
+ if (!bounded) continue;
3057
+ result.push(bounded);
3058
+ chars += bounded.length;
3059
+ }
3060
+ return result.reverse();
3061
+ }
3062
+
3063
+ export type PodNoResultLogRead = { tail: string[] } | { unavailable: string };
3064
+
3065
+ /**
3066
+ * POD-NORESULT-LOG-TAIL: the terminal Job (`backoffLimit 0`) is collected right after it fails, so the
3067
+ * container log has to be read while the Pod still exists. Never throws — an unreadable log becomes a reason.
3068
+ */
3069
+ export function readPodNoResultLogTail(args: {
3070
+ run: (args: readonly string[]) => { status: number | null; stdout: string; stderr: string };
3071
+ namespace: string;
3072
+ job: string;
3073
+ maxLines?: number;
3074
+ }): PodNoResultLogRead {
3075
+ const maxLines = args.maxLines ?? POD_NO_RESULT_LOG_TAIL_LINES;
3076
+ try {
3077
+ const result = args.run(['-n', args.namespace, 'logs', `job/${args.job}`, `--tail=${maxLines}`]);
3078
+ if (result.status !== 0) {
3079
+ const reason = boundedPodLogText(result.stderr || result.stdout || '', POD_NO_RESULT_LOG_REASON_MAX_CHARS);
3080
+ return { unavailable: reason || `kubectl logs exit ${result.status}` };
3081
+ }
3082
+ const tail = podNoResultLogTailLines(result.stdout, maxLines);
3083
+ return tail.length ? { tail } : { unavailable: 'empty log output' };
3084
+ } catch (error) {
3085
+ return { unavailable: boundedPodLogText(error instanceof Error ? error.message : String(error), POD_NO_RESULT_LOG_REASON_MAX_CHARS) || 'unknown error' };
3086
+ }
3087
+ }
3088
+
2757
3089
  /** 사람용 런 결과 한 줄 — 수확 브랜치가 있으면 «수확할 브랜치: …» 를 붙인다. */
2758
3090
  export function podRunResultLine(logs: string, branches: readonly string[]): string {
2759
3091
  const result = logs.split('\n').filter((line) => !line.startsWith('ELANOUS_MEM ')).join('\n').slice(-4000);