elanous 0.2.19 → 0.2.21

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 (509) 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/MObU8ujkMciAKr6ljAV05/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/088d8e8a.8069543d082bec12.js +160 -0
  5. package/apps/pwa/out/_next/static/chunks/1109.78a7a3288c0259d9.js +148 -0
  6. package/apps/pwa/out/_next/static/chunks/115.1c6690b8604f81c4.js +32 -0
  7. package/apps/pwa/out/_next/static/chunks/119fc678.6f591cdb84ca5791.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/1262.76a3f08e67c5f816.js +309 -0
  9. package/apps/pwa/out/_next/static/chunks/{4323.c5dfb6f503304775.js → 1268.0840ff91e083fcbd.js} +1 -1
  10. package/apps/pwa/out/_next/static/chunks/1307-c97eba9fb72a9a5e.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/1358d292.5c87f12cad6eaa91.js +122 -0
  12. package/apps/pwa/out/_next/static/chunks/1438.27a1a7b8f2308004.js +149 -0
  13. package/apps/pwa/out/_next/static/chunks/1442.02d85cd88f4c1eac.js +79 -0
  14. package/apps/pwa/out/_next/static/chunks/1501-1aad662d82ba43f7.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/1584.9e2dd53da6e6d4b1.js +24 -0
  16. package/apps/pwa/out/_next/static/chunks/1586.b989b0614afb77e6.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/1609.2ad355062870151f.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/1612-f6df0fc0eed7157b.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/1923.37f1d5fe7ebe3860.js +27 -0
  21. package/apps/pwa/out/_next/static/chunks/1948.5f35b39eef222a94.js +5 -0
  22. package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/1993.3fd074a2a246234e.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/{2977-e38442e86289f1d3.js → 211-9c9090c59e2c5907.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/2140.d2daf9ee96e295d1.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/2212.0359851457473efc.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/224.583e562365a52f74.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/2328.0f37465646bafa7d.js +36 -0
  31. package/apps/pwa/out/_next/static/chunks/2372.bf71372184991937.js +286 -0
  32. package/apps/pwa/out/_next/static/chunks/2489-ff55a35b202c0f94.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/2537-6c289376caa88ca1.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/2549-a8515c86f9e8cb96.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/2655.72cc6a97276c5acc.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/2743-ef6e8c3ced74cce5.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/2840-5458ad50c49cac6a.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/2856.f75d5baef7f49b74.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/3344.0de4b0ec5a454b7a.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/335.a13f339e97893437.js +94 -0
  45. package/apps/pwa/out/_next/static/chunks/3394.646348f34b2500be.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/3443.6276909b53d72d53.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/3495.abd4254d808bf2dc.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/3522-1de1319d028ac89c.js +25 -0
  49. package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/3721.ba5b82db0d9a5572.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/3919.9ff7dc25afefee33.js +74 -0
  54. package/apps/pwa/out/_next/static/chunks/3998.bd9a69d9b50e9bd2.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/4003.ab6db1dc266576c9.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/4144-fad095b48a1862ee.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/4216.e8ac94b9fe6b96ab.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/4327.c44dc2b55cb43ee5.js +34 -0
  59. package/apps/pwa/out/_next/static/chunks/4401.94257d497b6d26bc.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/4559.17ca2cdbaa8d414f.js +115 -0
  61. package/apps/pwa/out/_next/static/chunks/4565-3d7e3fdeb43686a8.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/4588.ad3364cb7fb97ada.js +86 -0
  63. package/apps/pwa/out/_next/static/chunks/4706.97e734bd4c9816ca.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/4795-f372490b178583fc.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/4982.6e44352f751b9a1b.js +77 -0
  66. package/apps/pwa/out/_next/static/chunks/5342.49c5c5a530e62b50.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/5360-08a80abdf3a4da99.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/5431.66c58ab1327ee0b7.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/5717.bcfcb96e701b82e0.js +90 -0
  70. package/apps/pwa/out/_next/static/chunks/5755-30d102586789bf7f.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/5957.3f1a4012388dd962.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/5977-59fedea50d13c762.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/602.9bf02ac8e9502598.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/6020.3a1b347e2d2dfacc.js +8 -0
  75. package/apps/pwa/out/_next/static/chunks/{4847.56f5bc454fbcc827.js → 6110.1297d4093fdfbb0d.js} +2 -2
  76. package/apps/pwa/out/_next/static/chunks/6158.566c890adc06a459.js +2 -0
  77. package/apps/pwa/out/_next/static/chunks/6375.fce1d174ba6d0324.js +64 -0
  78. package/apps/pwa/out/_next/static/chunks/6546.92a828aef441271f.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/658.957fdb0a8c6c6c8e.js +287 -0
  80. package/apps/pwa/out/_next/static/chunks/6706.323590aabb8b1293.js +226 -0
  81. package/apps/pwa/out/_next/static/chunks/6747.b67947f293a4c02d.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/6767.8e6c5f6e744fcd9f.js +38 -0
  83. package/apps/pwa/out/_next/static/chunks/6868.000b24a3045c5574.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/6946.bba74e9c96050f30.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/7033.a558f1b2f6912a53.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/7065.46029c26c53eac50.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/7087.6fae577d09249bc1.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/7175-350f4ce2873bc038.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/722.de8d9801e0f6d414.js +41 -0
  91. package/apps/pwa/out/_next/static/chunks/{1470.98a6ab61ccb4131c.js → 726.83532d69452ee24b.js} +1 -1
  92. package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/{6062-73e68343a9814f6a.js → 7327-2d69fb321ba73389.js} +1 -1
  94. package/apps/pwa/out/_next/static/chunks/7497.57981e9d7a75a52a.js +24 -0
  95. package/apps/pwa/out/_next/static/chunks/7630.2b9a68659053ff4e.js +193 -0
  96. package/apps/pwa/out/_next/static/chunks/7672.4af122ad892117c7.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/771.6ab889a233e7dae7.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.js +1 -0
  99. package/apps/pwa/out/_next/static/chunks/7899.597af4a9996c907f.js +1 -0
  100. package/apps/pwa/out/_next/static/chunks/79b78388.663188133449d707.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/8096.f7da0776cfde6300.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/8194.484f51e273d9abd2.js +217 -0
  105. package/apps/pwa/out/_next/static/chunks/8401-35e94e9091f5eb8d.js +1 -0
  106. package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +1 -0
  107. package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/8769.815ca69879f608b4.js +104 -0
  109. package/apps/pwa/out/_next/static/chunks/8857-f1762140ef9a96b1.js +1 -0
  110. package/apps/pwa/out/_next/static/chunks/88729267.9c6c000157c83f2f.js +62 -0
  111. package/apps/pwa/out/_next/static/chunks/8f835b4f.048eeb02299e3e12.js +1 -0
  112. package/apps/pwa/out/_next/static/chunks/9068.5f2764fccf86c1b2.js +93 -0
  113. package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +1 -0
  114. package/apps/pwa/out/_next/static/chunks/9103-60fbef33e66f0a46.js +1 -0
  115. package/apps/pwa/out/_next/static/chunks/9247-7581825d511a64c0.js +1 -0
  116. package/apps/pwa/out/_next/static/chunks/{7647.2fcd30d894d5ee30.js → 935.4ddaa99fc1d1126c.js} +1 -1
  117. package/apps/pwa/out/_next/static/chunks/9451.ebfb0dcc9c02250f.js +23 -0
  118. package/apps/pwa/out/_next/static/chunks/9497-b0e758d1cd4ae8d7.js +1 -0
  119. package/apps/pwa/out/_next/static/chunks/9559.e648369b1d57cf9a.js +62 -0
  120. package/apps/pwa/out/_next/static/chunks/9832.2ab2e818013574c7.js +1 -0
  121. package/apps/pwa/out/_next/static/chunks/a9f2b54d-40c16bd0efdb04b8.js +1 -0
  122. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +1 -0
  123. package/apps/pwa/out/_next/static/chunks/app/approvals/page-937c92292a4ab4f0.js +1 -0
  124. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-d781be3e3a9cfd74.js +1 -0
  125. package/apps/pwa/out/_next/static/chunks/app/board/page-b0a80997903852d8.js +1 -0
  126. package/apps/pwa/out/_next/static/chunks/app/botlab/page-2b1263165630d9c8.js +1 -0
  127. package/apps/pwa/out/_next/static/chunks/app/bots/page-ea998f7f5d156bea.js +1 -0
  128. package/apps/pwa/out/_next/static/chunks/app/ceo/page-33c328b64ca508f8.js +1 -0
  129. package/apps/pwa/out/_next/static/chunks/app/chat/page-e2d6bed48ac20120.js +1 -0
  130. package/apps/pwa/out/_next/static/chunks/app/consult/page-111cabe4d1fd69ae.js +1 -0
  131. package/apps/pwa/out/_next/static/chunks/app/control/page-544dd4bd4847eeb7.js +1 -0
  132. package/apps/pwa/out/_next/static/chunks/app/decisions/page-3ee191319bf97d87.js +1 -0
  133. package/apps/pwa/out/_next/static/chunks/app/design-check/page-e4bcb2c503dab04b.js +1 -0
  134. package/apps/pwa/out/_next/static/chunks/app/editor/page-300897dcbbd5ed76.js +1 -0
  135. package/apps/pwa/out/_next/static/chunks/app/exec/page-a449f1ed472b81c2.js +1 -0
  136. package/apps/pwa/out/_next/static/chunks/app/field/page-9045094d6d3b5cd0.js +1 -0
  137. package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +1 -0
  138. package/apps/pwa/out/_next/static/chunks/app/inside/page-c06673596849ef45.js +1 -0
  139. package/apps/pwa/out/_next/static/chunks/app/intake/page-103fa3191c1b2e08.js +1 -0
  140. package/apps/pwa/out/_next/static/chunks/app/layout-9f81233f29dcd409.js +1 -0
  141. package/apps/pwa/out/_next/static/chunks/app/live/{page-b70cd005d9169e25.js → page-be8b4615c919e36c.js} +1 -1
  142. package/apps/pwa/out/_next/static/chunks/app/live-run/page-c685b104e0dfa609.js +1 -0
  143. package/apps/pwa/out/_next/static/chunks/app/loops/page-8d693331cfbb6add.js +1 -0
  144. package/apps/pwa/out/_next/static/chunks/app/market/page-cef4f30a4c22fae1.js +1 -0
  145. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66ea1b54fc83fa15.js +1 -0
  146. package/apps/pwa/out/_next/static/chunks/app/morning/page-7a12f61be4a579d1.js +1 -0
  147. package/apps/pwa/out/_next/static/chunks/app/not-found-c85b0589591a99c9.js +1 -0
  148. package/apps/pwa/out/_next/static/chunks/app/observatory/page-725a4da02dd129eb.js +1 -0
  149. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-8cc707bd09a497d9.js +1 -0
  150. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-90b55be1f15ce0e8.js +1 -0
  151. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cb16606e08be65db.js +1 -0
  152. package/apps/pwa/out/_next/static/chunks/app/outputs/page-16dc64b74ed6a40a.js +1 -0
  153. package/apps/pwa/out/_next/static/chunks/app/page-21bec4afeb2f0917.js +1 -0
  154. package/apps/pwa/out/_next/static/chunks/app/reflection/page-dc39ee7a36d26385.js +1 -0
  155. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-84ae759f76053672.js +1 -0
  156. package/apps/pwa/out/_next/static/chunks/app/sessions/page-3db58ddb61fe2be9.js +1 -0
  157. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c5315e30f3528dc4.js +1 -0
  158. package/apps/pwa/out/_next/static/chunks/app/settings/page-2fe368efb6de3b6a.js +1 -0
  159. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-8d04132dd0ac5e10.js +1 -0
  160. package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +1 -0
  161. package/apps/pwa/out/_next/static/chunks/app/setup/page-f23484de467c9ea3.js +1 -0
  162. package/apps/pwa/out/_next/static/chunks/app/share/page-c69762987b751174.js +1 -0
  163. package/apps/pwa/out/_next/static/chunks/app/showroom/page-5456692bda39aafb.js +1 -0
  164. package/apps/pwa/out/_next/static/chunks/app/tasks/page-d75f7cd65845ce98.js +1 -0
  165. package/apps/pwa/out/_next/static/chunks/app/term/page-5603f5e568b25ce3.js +1 -0
  166. package/apps/pwa/out/_next/static/chunks/app/today/page-d2cc0f4e89f42028.js +1 -0
  167. package/apps/pwa/out/_next/static/chunks/app/trace/page-a6eb73b8a4840f1c.js +1 -0
  168. package/apps/pwa/out/_next/static/chunks/app/vault/page-c0a3f4d10edb83fa.js +1 -0
  169. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6c0b88068c143f15.js +1 -0
  170. package/apps/pwa/out/_next/static/chunks/app/workflows/page-7f8d7fcdf058a845.js +1 -0
  171. package/apps/pwa/out/_next/static/chunks/app/workspace/page-22163556cd38bc88.js +1 -0
  172. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-141436fd26dd56fc.js +1 -0
  173. package/apps/pwa/out/_next/static/chunks/d70879db.c66c56a95a91efc6.js +66 -0
  174. package/apps/pwa/out/_next/static/chunks/{8046cadf-211ca3c0e00f2b72.js → f6231284-45726550aba08fca.js} +1 -1
  175. package/apps/pwa/out/_next/static/chunks/{2ea96958.e8c7e112de50b067.js → f6b7c9ba.64a895b8b2fedbdc.js} +1 -1
  176. package/apps/pwa/out/_next/static/chunks/f8626309.a4e5d362b1d55e59.js +1 -0
  177. package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +1 -0
  178. package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +1 -0
  179. package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +1 -0
  180. package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +1 -0
  181. package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +1 -0
  182. package/apps/pwa/out/_next/static/chunks/webpack-f2b4016b9b981bc8.js +1 -0
  183. package/apps/pwa/out/_next/static/css/{d3427996da11e480.css → b9d8151e48998417.css} +1 -1
  184. package/apps/pwa/out/_next/static/css/e81df11490b4413b.css +1 -0
  185. package/apps/pwa/out/approvals/index.html +1 -1
  186. package/apps/pwa/out/approvals/index.txt +23 -23
  187. package/apps/pwa/out/autopilot/index.html +1 -1
  188. package/apps/pwa/out/autopilot/index.txt +23 -23
  189. package/apps/pwa/out/board/index.html +1 -1
  190. package/apps/pwa/out/board/index.txt +23 -23
  191. package/apps/pwa/out/botlab/index.html +1 -1
  192. package/apps/pwa/out/botlab/index.txt +22 -22
  193. package/apps/pwa/out/bots/index.html +1 -1
  194. package/apps/pwa/out/bots/index.txt +22 -22
  195. package/apps/pwa/out/ceo/index.html +1 -1
  196. package/apps/pwa/out/ceo/index.txt +23 -23
  197. package/apps/pwa/out/chat/index.html +1 -1
  198. package/apps/pwa/out/chat/index.txt +22 -22
  199. package/apps/pwa/out/consult/index.html +1 -1
  200. package/apps/pwa/out/consult/index.txt +23 -23
  201. package/apps/pwa/out/control/index.html +1 -1
  202. package/apps/pwa/out/control/index.txt +23 -23
  203. package/apps/pwa/out/decisions/index.html +1 -1
  204. package/apps/pwa/out/decisions/index.txt +22 -22
  205. package/apps/pwa/out/design-check/index.html +1 -1
  206. package/apps/pwa/out/design-check/index.txt +23 -23
  207. package/apps/pwa/out/editor/index.html +1 -1
  208. package/apps/pwa/out/editor/index.txt +24 -24
  209. package/apps/pwa/out/exec/index.html +1 -1
  210. package/apps/pwa/out/exec/index.txt +23 -23
  211. package/apps/pwa/out/field/index.html +1 -1
  212. package/apps/pwa/out/field/index.txt +23 -23
  213. package/apps/pwa/out/healthz +1 -1
  214. package/apps/pwa/out/index.html +1 -1
  215. package/apps/pwa/out/index.txt +22 -22
  216. package/apps/pwa/out/inside/index.html +1 -1
  217. package/apps/pwa/out/inside/index.txt +23 -23
  218. package/apps/pwa/out/intake/index.html +1 -1
  219. package/apps/pwa/out/intake/index.txt +23 -23
  220. package/apps/pwa/out/live/index.html +1 -1
  221. package/apps/pwa/out/live/index.txt +23 -23
  222. package/apps/pwa/out/live-run/index.html +1 -0
  223. package/apps/pwa/out/live-run/index.txt +35 -0
  224. package/apps/pwa/out/loops/index.html +1 -1
  225. package/apps/pwa/out/loops/index.txt +22 -22
  226. package/apps/pwa/out/market/index.html +1 -1
  227. package/apps/pwa/out/market/index.txt +22 -22
  228. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  229. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  230. package/apps/pwa/out/morning/index.html +1 -1
  231. package/apps/pwa/out/morning/index.txt +23 -23
  232. package/apps/pwa/out/observatory/index.html +1 -1
  233. package/apps/pwa/out/observatory/index.txt +23 -23
  234. package/apps/pwa/out/ops/checklist/index.html +1 -1
  235. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  236. package/apps/pwa/out/ops/release/index.html +1 -1
  237. package/apps/pwa/out/ops/release/index.txt +22 -22
  238. package/apps/pwa/out/ops/seats/index.html +1 -1
  239. package/apps/pwa/out/ops/seats/index.txt +22 -22
  240. package/apps/pwa/out/outputs/index.html +1 -1
  241. package/apps/pwa/out/outputs/index.txt +22 -22
  242. package/apps/pwa/out/reflection/index.html +1 -1
  243. package/apps/pwa/out/reflection/index.txt +23 -23
  244. package/apps/pwa/out/scheduler/index.html +1 -1
  245. package/apps/pwa/out/scheduler/index.txt +23 -23
  246. package/apps/pwa/out/sessions/index.html +1 -1
  247. package/apps/pwa/out/sessions/index.txt +22 -22
  248. package/apps/pwa/out/settings/devices/index.html +1 -1
  249. package/apps/pwa/out/settings/devices/index.txt +23 -23
  250. package/apps/pwa/out/settings/index.html +1 -1
  251. package/apps/pwa/out/settings/index.txt +23 -23
  252. package/apps/pwa/out/setup/done/index.html +1 -1
  253. package/apps/pwa/out/setup/done/index.txt +23 -23
  254. package/apps/pwa/out/setup/index.html +1 -1
  255. package/apps/pwa/out/setup/index.txt +23 -23
  256. package/apps/pwa/out/share/index.html +1 -1
  257. package/apps/pwa/out/share/index.txt +23 -23
  258. package/apps/pwa/out/showroom/index.html +1 -1
  259. package/apps/pwa/out/showroom/index.txt +22 -22
  260. package/apps/pwa/out/tasks/index.html +1 -1
  261. package/apps/pwa/out/tasks/index.txt +23 -23
  262. package/apps/pwa/out/term/index.html +1 -1
  263. package/apps/pwa/out/term/index.txt +23 -23
  264. package/apps/pwa/out/today/index.html +1 -1
  265. package/apps/pwa/out/today/index.txt +23 -23
  266. package/apps/pwa/out/trace/index.html +1 -1
  267. package/apps/pwa/out/trace/index.txt +23 -23
  268. package/apps/pwa/out/vault/index.html +1 -1
  269. package/apps/pwa/out/vault/index.txt +23 -23
  270. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  271. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  272. package/apps/pwa/out/workflows/index.html +1 -1
  273. package/apps/pwa/out/workflows/index.txt +23 -23
  274. package/apps/pwa/out/workspace/index.html +1 -1
  275. package/apps/pwa/out/workspace/index.txt +23 -23
  276. package/apps/pwa/out/worktrees/index.html +1 -1
  277. package/apps/pwa/out/worktrees/index.txt +23 -23
  278. package/graphs/demo/editor-recipes.yaml +22 -0
  279. package/graphs/drafts/recipes.yaml +4 -4
  280. package/graphs/implement-loop.yaml +277 -5
  281. package/graphs/retro/daily.yaml +25 -0
  282. package/graphs/retro/recipes.yaml +24 -0
  283. package/package.json +1 -1
  284. package/scripts/ask-marker-check.ts +3 -1
  285. package/scripts/coord-channel-watch.sh +31 -4
  286. package/scripts/docs-lint.ts +4 -1
  287. package/scripts/land-global-checks.ts +89 -0
  288. package/scripts/lib/pch-regress-map.ts +2 -1
  289. package/scripts/marketing/generate-ax-brief.ts +18 -1
  290. package/scripts/measure-nl-routing-corpus.ts +11 -3
  291. package/scripts/release-loop/gate-node.ts +296 -25
  292. package/scripts/release-loop/gate-timing.ts +116 -7
  293. package/scripts/release-loop/preflight.ts +6 -4
  294. package/scripts/release-loop/publish-window.ts +141 -0
  295. package/scripts/release-loop/release-docs.ts +1 -4
  296. package/scripts/release-loop/release-universe.ts +38 -0
  297. package/scripts/release-loop/resume-release.ts +4 -1
  298. package/scripts/release-loop/shard-plan.ts +50 -5
  299. package/scripts/release-loop/unattended-release.ts +10 -2
  300. package/scripts/release-story/draft.ts +5 -1
  301. package/scripts/retro/shadow-compare.ts +71 -0
  302. package/scripts/rhythm/daily.ts +8 -2
  303. package/src/agent-mission/driver.ts +34 -6
  304. package/src/autopilot/build/llm-conflict-merge.ts +132 -7
  305. package/src/autopilot/build/merge-intent.ts +135 -7
  306. package/src/away/release-watch.ts +20 -3
  307. package/src/browser-cdp/client.ts +10 -5
  308. package/src/cli/config-drift.ts +513 -0
  309. package/src/cli/log-event-names.ts +4 -0
  310. package/src/cli/ops-cli.ts +11 -0
  311. package/src/cli/ops-overview-cli.ts +50 -0
  312. package/src/cli/pod-cli.ts +1 -0
  313. package/src/cli/pr-cli.ts +20 -0
  314. package/src/cli/release-cli.ts +20 -5
  315. package/src/cli/retro-cli.ts +78 -0
  316. package/src/cli/tasks-cli.ts +200 -8
  317. package/src/dashboard/index.ts +40 -4
  318. package/src/dashboard/slash-runtime/dashboard-handlers.ts +5 -4
  319. package/src/dashboard/slash-runtime/help-from-registry.ts +57 -31
  320. package/src/domains/outbound-log-probe.ts +6 -1
  321. package/src/esc-abort-gate.ts +3 -1
  322. package/src/graph-runner/graph-cli.ts +57 -0
  323. package/src/graph-runner/graph-run-api.ts +218 -0
  324. package/src/graph-runner/runner.ts +11 -1
  325. package/src/graph-wizard/archetypes.ts +88 -0
  326. package/src/graph-wizard/generate.ts +545 -0
  327. package/src/graph-wizard/saved-steps.ts +120 -0
  328. package/src/graph-wizard/steps.ts +193 -0
  329. package/src/harness/harness-cli-command.ts +482 -59
  330. package/src/harness/harness-queue.ts +315 -25
  331. package/src/harness/harness-salvage-cli.ts +62 -0
  332. package/src/hq/fence-outcomes.ts +63 -0
  333. package/src/hq/fence-wrapper.ts +33 -0
  334. package/src/hq/hq.ts +11 -1
  335. package/src/index.ts +76 -10
  336. package/src/intake-plane/digest.ts +18 -11
  337. package/src/maturity/feature-maturity.ts +4 -3
  338. package/src/nexus/api/draft-metrics.ts +114 -0
  339. package/src/nexus/api/graph-kinds.ts +10 -4
  340. package/src/nexus/api/graph-wizard-api.ts +53 -0
  341. package/src/nexus/api/graphs-api.ts +220 -10
  342. package/src/nexus/api/harness-traversal.ts +205 -0
  343. package/src/nexus/api/http-server.ts +69 -3
  344. package/src/nexus/api/loop-edges.ts +4 -2
  345. package/src/nexus/api/ops-api.ts +22 -7
  346. package/src/nexus/api/ops-board-route.ts +48 -0
  347. package/src/nexus/outbound/router.ts +28 -8
  348. package/src/onboarding/entry-hints.ts +5 -0
  349. package/src/onboarding/first-run.ts +3 -0
  350. package/src/onboarding.ts +24 -6
  351. package/src/ops/overview-collect.ts +345 -0
  352. package/src/ops/overview-seat-era.ts +90 -0
  353. package/src/ops/overview.ts +223 -0
  354. package/src/release-loop/feature-store.ts +14 -11
  355. package/src/release-loop/gate-shards.ts +163 -0
  356. package/src/release-loop/release-schedule.ts +6 -2
  357. package/src/retro/daily.ts +372 -0
  358. package/src/seat-loop/op-decide.ts +173 -0
  359. package/src/seat-loop/seat-loop.ts +60 -3
  360. package/src/seat-loop/seat-report.ts +58 -3
  361. package/src/self-dev/ask-launch-flow.ts +9 -6
  362. package/src/self-dev/ask-launch-io.ts +52 -2
  363. package/src/self-dev/ask-path-candidates.ts +40 -2
  364. package/src/self-dev/draft-sweep.ts +371 -16
  365. package/src/self-dev/draft-triage-rules.ts +19 -4
  366. package/src/self-dev/graph-journey-nodes.ts +157 -0
  367. package/src/self-dev/graph-journey-route.ts +105 -0
  368. package/src/self-dev/launch-preflight.ts +43 -3
  369. package/src/self-dev/pr-preview.ts +84 -16
  370. package/src/self-dev/release-path-guard.ts +7 -4
  371. package/src/self-implement/blocked-draft-policy.ts +101 -0
  372. package/src/self-implement/execution-clarification.ts +11 -0
  373. package/src/self-implement/frozen-merges.ts +23 -0
  374. package/src/self-implement/gate-env-retry.ts +83 -0
  375. package/src/self-implement/goal-author.ts +14 -0
  376. package/src/self-implement/heal-intake.ts +3 -0
  377. package/src/self-implement/orchestrator.ts +234 -6
  378. package/src/self-implement/run-stop.ts +123 -0
  379. package/src/self-implement/run-ttl.ts +258 -0
  380. package/src/self-implement/salvage-retention.ts +123 -0
  381. package/src/self-implement/seams.ts +7 -0
  382. package/src/self-implement/self-implement-cli.ts +2 -0
  383. package/src/self-implement/self-implement-runtime.ts +12 -0
  384. package/src/self-implement/sibling-resync.ts +223 -0
  385. package/src/self-implement/stop-autoheal.ts +204 -0
  386. package/src/session-runtime/index.ts +5 -3
  387. package/src/shell-env-bootstrap.ts +17 -3
  388. package/src/task-agent/board.ts +1 -1
  389. package/src/task-agent/card-evidence.ts +105 -10
  390. package/src/task-agent/judge.ts +7 -0
  391. package/src/task-agent/live-moves.ts +120 -20
  392. package/src/task-agent/mission.ts +1 -1
  393. package/src/task-agent/parent-host.ts +315 -0
  394. package/src/task-agent/shadow.ts +10 -1
  395. package/src/task-agent/task-hand.ts +16 -4
  396. package/src/task-orchestrator/surfaces/pod-command-job.ts +60 -11
  397. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +4 -0
  398. package/src/task-orchestrator/surfaces/pod-gate-deps.ts +123 -0
  399. package/src/task-orchestrator/surfaces/pod-install-slots.ts +89 -0
  400. package/src/task-orchestrator/surfaces/pod-lease.ts +93 -10
  401. package/src/task-orchestrator/surfaces/pod-pool.ts +147 -34
  402. package/src/task-orchestrator/surfaces/self-implement-pod.ts +58 -9
  403. package/src/telegram-commands.ts +41 -0
  404. package/src/testing/real-cli-spawn.ts +106 -0
  405. package/src/tool-runtime/browser-runtime.ts +87 -3
  406. package/src/tool-runtime/index.ts +1 -0
  407. package/src/user-config.ts +76 -7
  408. package/src/version/packed-revision.json +1 -1
  409. package/apps/pwa/out/_next/static/J5H7BvqgSI8DUjtRRcA_S/_buildManifest.js +0 -1
  410. package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +0 -1
  411. package/apps/pwa/out/_next/static/chunks/1157-cba0e8553d35a980.js +0 -1
  412. package/apps/pwa/out/_next/static/chunks/171-23a877ed11720de9.js +0 -1
  413. package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +0 -1
  414. package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +0 -1
  415. package/apps/pwa/out/_next/static/chunks/2336-e830c777eb6bce04.js +0 -1
  416. package/apps/pwa/out/_next/static/chunks/2375-e061bb24cd2b5cde.js +0 -1
  417. package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +0 -1
  418. package/apps/pwa/out/_next/static/chunks/3094.67339318c7f72a3a.js +0 -2
  419. package/apps/pwa/out/_next/static/chunks/338-ceb956612dcfaa86.js +0 -1
  420. package/apps/pwa/out/_next/static/chunks/3395-835e4ceaa6335ba4.js +0 -1
  421. package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +0 -5
  422. package/apps/pwa/out/_next/static/chunks/3441-be82e3595aef3a20.js +0 -1
  423. package/apps/pwa/out/_next/static/chunks/3689-89905039bf3e7257.js +0 -1
  424. package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +0 -1
  425. package/apps/pwa/out/_next/static/chunks/3822-6f268bc26c6a84ac.js +0 -1
  426. package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +0 -1
  427. package/apps/pwa/out/_next/static/chunks/4457-d7df81c90f9d0268.js +0 -1
  428. package/apps/pwa/out/_next/static/chunks/4551-77dadae76f6cbace.js +0 -1
  429. package/apps/pwa/out/_next/static/chunks/4978.6fd93b974441e87f.js +0 -1
  430. package/apps/pwa/out/_next/static/chunks/5012-f746f6549b869f91.js +0 -1
  431. package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +0 -1
  432. package/apps/pwa/out/_next/static/chunks/5791-bb82743fa6299562.js +0 -1
  433. package/apps/pwa/out/_next/static/chunks/62-5bfed1e23790b745.js +0 -1
  434. package/apps/pwa/out/_next/static/chunks/6320-9bc8561fde416e07.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/649-ca96d1c5d09e02b8.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/6726-85a7db59b288c53f.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/6735-33267c4630d0d963.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/7633-141682015873beb6.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/8067-143791231c99bd02.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +0 -1
  445. package/apps/pwa/out/_next/static/chunks/8430-a10cfba8ef282d4e.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/8870-9614a213b990b10b.js +0 -25
  447. package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/9454-12c2f30dea62291c.js +0 -1
  449. package/apps/pwa/out/_next/static/chunks/976-4a9b4ebdca7c739a.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/9989-15cdf568bafa9fc8.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/ae787e4c-e5b1ef00b2dbceca.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/app/approvals/page-deef5ee191830f14.js +0 -1
  454. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-b193da485f9a14b2.js +0 -1
  455. package/apps/pwa/out/_next/static/chunks/app/board/page-b6e1a79df0e214f4.js +0 -1
  456. package/apps/pwa/out/_next/static/chunks/app/botlab/page-e84df4d907f703c7.js +0 -1
  457. package/apps/pwa/out/_next/static/chunks/app/bots/page-a5fb9a5db89ae4ea.js +0 -1
  458. package/apps/pwa/out/_next/static/chunks/app/ceo/page-1499f481d7fc52d6.js +0 -1
  459. package/apps/pwa/out/_next/static/chunks/app/chat/page-c7cf753aeb648790.js +0 -1
  460. package/apps/pwa/out/_next/static/chunks/app/consult/page-4118469e514e10fd.js +0 -1
  461. package/apps/pwa/out/_next/static/chunks/app/control/page-e968f7630654332a.js +0 -1
  462. package/apps/pwa/out/_next/static/chunks/app/decisions/page-505915052f947022.js +0 -1
  463. package/apps/pwa/out/_next/static/chunks/app/design-check/page-c2f962fb4a3b06c6.js +0 -1
  464. package/apps/pwa/out/_next/static/chunks/app/editor/page-7b5ed0bd8a589ec4.js +0 -1
  465. package/apps/pwa/out/_next/static/chunks/app/exec/page-cf8193250dd42dd4.js +0 -1
  466. package/apps/pwa/out/_next/static/chunks/app/field/page-cf3f5dfa35dea8e9.js +0 -1
  467. package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +0 -1
  468. package/apps/pwa/out/_next/static/chunks/app/inside/page-5b29d9b06ceadade.js +0 -1
  469. package/apps/pwa/out/_next/static/chunks/app/intake/page-a15e7fcc283e9ca7.js +0 -1
  470. package/apps/pwa/out/_next/static/chunks/app/layout-d2915e7b70ff3d0e.js +0 -1
  471. package/apps/pwa/out/_next/static/chunks/app/loops/page-2595f0eba29f57e1.js +0 -1
  472. package/apps/pwa/out/_next/static/chunks/app/market/page-c776322454156e36.js +0 -1
  473. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-77bbd17f4435aa10.js +0 -1
  474. package/apps/pwa/out/_next/static/chunks/app/morning/page-049689e611ecc936.js +0 -1
  475. package/apps/pwa/out/_next/static/chunks/app/not-found-85954a4fb09c44eb.js +0 -1
  476. package/apps/pwa/out/_next/static/chunks/app/observatory/page-e773e552e1068ee6.js +0 -1
  477. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-5633c4bfa7684b4a.js +0 -1
  478. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-d829b92bd01b4994.js +0 -1
  479. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-061c317fc771becf.js +0 -1
  480. package/apps/pwa/out/_next/static/chunks/app/outputs/page-3a45ece8865ed4b6.js +0 -1
  481. package/apps/pwa/out/_next/static/chunks/app/page-0be990618c0d56bd.js +0 -1
  482. package/apps/pwa/out/_next/static/chunks/app/reflection/page-bfea4865dc244b73.js +0 -1
  483. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-0d6ba8e337741484.js +0 -1
  484. package/apps/pwa/out/_next/static/chunks/app/sessions/page-76a88b55d816482e.js +0 -1
  485. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-bba1542df59f61ce.js +0 -1
  486. package/apps/pwa/out/_next/static/chunks/app/settings/page-4185c95689bb372e.js +0 -1
  487. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b699e1a8d5d34b8b.js +0 -1
  488. package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +0 -1
  489. package/apps/pwa/out/_next/static/chunks/app/setup/page-c225e338c31bf274.js +0 -1
  490. package/apps/pwa/out/_next/static/chunks/app/share/page-449353f70507ca1f.js +0 -1
  491. package/apps/pwa/out/_next/static/chunks/app/showroom/page-90b889714102476d.js +0 -1
  492. package/apps/pwa/out/_next/static/chunks/app/tasks/page-f1ee7fbeb7f96ddc.js +0 -1
  493. package/apps/pwa/out/_next/static/chunks/app/term/page-d59a95e1434dfaa0.js +0 -1
  494. package/apps/pwa/out/_next/static/chunks/app/today/page-71491a23414d1a47.js +0 -1
  495. package/apps/pwa/out/_next/static/chunks/app/trace/page-de7112bdb65fa4fe.js +0 -1
  496. package/apps/pwa/out/_next/static/chunks/app/vault/page-feb4d0d88079660d.js +0 -1
  497. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f17d887b40dd6b1d.js +0 -1
  498. package/apps/pwa/out/_next/static/chunks/app/workflows/page-8da77be2192bcd5d.js +0 -1
  499. package/apps/pwa/out/_next/static/chunks/app/workspace/page-72c3b9a5e9582b29.js +0 -1
  500. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3afe4c8d0c48c39.js +0 -1
  501. package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +0 -1
  502. package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +0 -1
  503. package/apps/pwa/out/_next/static/chunks/main-app-b9308f2d8a931531.js +0 -1
  504. package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +0 -1
  505. package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +0 -1
  506. package/apps/pwa/out/_next/static/chunks/webpack-6f35f9d892fa630b.js +0 -1
  507. package/apps/pwa/out/_next/static/css/85f53c6f35d11382.css +0 -1
  508. /package/apps/pwa/out/_next/static/{J5H7BvqgSI8DUjtRRcA_S → MObU8ujkMciAKr6ljAV05}/_ssgManifest.js +0 -0
  509. /package/skills/project-onboarding/{skill.md → SKILL.md} +0 -0
@@ -13,7 +13,9 @@ import { getUserConfig } from '../../user-config.js';
13
13
  import { debug } from '../../debug/log.js';
14
14
  import { effectiveInstanceRoot } from '../../instance/resolve.js';
15
15
  import { collectPodArtifacts } from './pod-artifact-return.js';
16
+ import { getDefaultLogStore, type LogStore } from '../../mss/logging/log-store.js';
16
17
  import { podBunCacheVolume } from './pod-bun-cache.js';
18
+ import { podHostDirsInit, podInstallSlotsVolume } from './pod-install-slots.js';
17
19
  import { type PodPoolMember, PodPoolScheduler, resolvePodPoolSpec, parsePodPool, checkPodPool, syncPoolImages, type PoolKubectl } from './pod-pool.js';
18
20
  import { podSkillsDigest, readSkillEnvFiles, resolvePodSkills } from './pod-skills.js';
19
21
  import { podSourceScript, type PodSource } from './pod-source-receive.js';
@@ -135,6 +137,19 @@ for i in $(seq 1 60); do
135
137
  done
136
138
  echo "[gate] ISOLATION NOT ENFORCED within 30s"; exit 1`;
137
139
 
140
+ /** Export the command's log store into the existing outbox return channel without changing the command exit status. */
141
+ const COMMAND_LOG_EXPORT = `if mkdir -p "$HOME/outbox/pod-logs" && elanous --test="$HOME/.elanous-test" logs --instance prod --since 12h --limit 20000 --json --json-data > "$HOME/outbox/pod-logs/logs.jsonl"; then
142
+ logs_size=$(( $(wc -c < "$HOME/outbox/pod-logs/logs.jsonl") ))
143
+ if [ "$logs_size" -gt 5242880 ]; then
144
+ tail -c 5242880 "$HOME/outbox/pod-logs/logs.jsonl" | tail -n +2 > "$HOME/outbox/pod-logs/logs.jsonl.tail" && mv -f "$HOME/outbox/pod-logs/logs.jsonl.tail" "$HOME/outbox/pod-logs/logs.jsonl"
145
+ printf 'ELANOUS_POD_LOGS_TRUNCATED %s %s\\n' "$logs_size" "$(( $(wc -c < "$HOME/outbox/pod-logs/logs.jsonl") ))"
146
+ fi
147
+ else
148
+ logs_rc=$?
149
+ rm -f "$HOME/outbox/pod-logs/logs.jsonl"
150
+ printf 'ELANOUS_POD_LOGS_UNAVAILABLE export-exit-%s\\n' "$logs_rc"
151
+ fi`;
152
+
138
153
  /** 기존 Job 과 같은 `~/outbox` 조각 형식(`ELANOUS_POD_ARTIFACT`). 원장 회수는 하지 않는다. */
139
154
  const ARTIFACT_EMIT = `set -o pipefail
140
155
  artifact_bytes=0
@@ -171,11 +186,15 @@ export interface PodCommandJobInput {
171
186
  repoUrl: string;
172
187
  command: readonly string[];
173
188
  skills: readonly string[];
189
+ /** pod run opt-in: return this command's local log store with its outbox artifacts. */
190
+ returnLogs?: boolean;
174
191
  llm?: 'grok';
175
192
  /** 저장소를 clone 한다 — 비공개 저장소라 GitHub 토큰이 Secret 으로 간다(명시 opt-in). 기본은 clone 없이 `~/work` 에서 이미지의 `elanous` 로 돈다. */
176
193
  clone?: boolean;
177
194
  hostMirror?: string;
178
195
  bunCache?: string;
196
+ /** GATE-INSTALL-CACHE — hostPath of the node-wide install tokens (release gate shards only · `pod-install-slots.ts`). */
197
+ installSlots?: string;
179
198
  /** 컨테이너 메모리 한도(기본 16Gi) — 게이트가 파일 하나 격리 Job 에 올린다. */
180
199
  memoryLimit?: string;
181
200
  /** GATE-SPEED A3①: CPU request/limit override (release gate shards only). Omitted = request 1 / limit 4. */
@@ -197,6 +216,9 @@ export function podCommandJobManifest(o: PodCommandJobInput): Record<string, unk
197
216
  }
198
217
  const grok = o.llm === 'grok';
199
218
  const bunCache = o.bunCache ? podBunCacheVolume(o.bunCache) : undefined;
219
+ const installSlots = o.installSlots ? podInstallSlotsVolume(o.installSlots) : undefined;
220
+ // hostPath directories the uid-1000 child writes: a root init container hands them over (DirectoryOrCreate is root 0755).
221
+ const hostDirMounts = [...(bunCache ? [bunCache.volumeMount] : []), ...(installSlots ? [installSlots.volumeMount] : [])];
200
222
  const cpu = resolvePodCpu(o.cpu);
201
223
  const quotedArgs = o.command.map(bashSingleQuote).join(' ');
202
224
  const credLines = [
@@ -216,6 +238,8 @@ export function podCommandJobManifest(o: PodCommandJobInput): Record<string, unk
216
238
  `set -- ${quotedArgs}`,
217
239
  '"$@"',
218
240
  'rc=$?',
241
+ // A command may not end in LF; keep artifact markers on their own line for the existing parser.
242
+ ...(o.returnLogs ? ["printf '\\n'", COMMAND_LOG_EXPORT] : []),
219
243
  ARTIFACT_EMIT,
220
244
  'exit $rc',
221
245
  ].join('\n');
@@ -233,7 +257,10 @@ export function podCommandJobManifest(o: PodCommandJobInput): Record<string, unk
233
257
  spec: {
234
258
  restartPolicy: 'Never',
235
259
  securityContext: { runAsUser: 1000, fsGroup: 1000 },
236
- initContainers: [{ name: 'isolation-gate', image: o.image, imagePullPolicy: o.imagePullPolicy ?? 'Never', command: ['bash', '-c'], args: [GATE] }],
260
+ initContainers: [
261
+ { name: 'isolation-gate', image: o.image, imagePullPolicy: o.imagePullPolicy ?? 'Never', command: ['bash', '-c'], args: [GATE] },
262
+ ...(hostDirMounts.length ? [podHostDirsInit(o.image, o.imagePullPolicy ?? 'Never', hostDirMounts)] : []),
263
+ ],
237
264
  containers: [{
238
265
  name: 'child', image: o.image, imagePullPolicy: o.imagePullPolicy ?? 'Never',
239
266
  resources: { requests: { ...POD_CHILD_REQUESTS, cpu: cpu.request, memory: memoryRequestWithin(o.memoryLimit) }, limits: { memory: o.memoryLimit ?? '16Gi', cpu: cpu.limit } },
@@ -243,16 +270,17 @@ export function podCommandJobManifest(o: PodCommandJobInput): Record<string, unk
243
270
  { name: 'ELANOUS_SUBSTRATE', value: 'pod' },
244
271
  { name: 'ELANOUS_POD_NAME', valueFrom: { fieldRef: { fieldPath: 'metadata.name' } } },
245
272
  ],
246
- ...(secretKeys || o.hostMirror || bunCache ? { volumeMounts: [
273
+ ...(secretKeys || o.hostMirror || hostDirMounts.length ? { volumeMounts: [
247
274
  ...(secretKeys ? [{ name: 'creds', mountPath: '/creds', readOnly: true }] : []),
248
275
  ...(o.hostMirror ? [{ name: 'host-mirror', mountPath: '/host-mirror', readOnly: true }] : []),
249
- ...(bunCache ? [bunCache.volumeMount] : []),
276
+ ...hostDirMounts,
250
277
  ] } : {}),
251
278
  }],
252
- ...(secretKeys || o.hostMirror || bunCache ? { volumes: [
279
+ ...(secretKeys || o.hostMirror || hostDirMounts.length ? { volumes: [
253
280
  ...(secretKeys ? [{ name: 'creds', secret: { secretName: `${o.name}-creds`, defaultMode: 0o400 } }] : []),
254
281
  ...(o.hostMirror ? [{ name: 'host-mirror', hostPath: { path: o.hostMirror, type: 'Directory' } }] : []),
255
282
  ...(bunCache ? [bunCache.volume] : []),
283
+ ...(installSlots ? [installSlots.volume] : []),
256
284
  ] } : {}),
257
285
  },
258
286
  },
@@ -315,12 +343,16 @@ export interface PodCommandResult {
315
343
 
316
344
  export interface RunPodCommandOptions {
317
345
  command: readonly string[];
346
+ /** pod run opt-in; other runPodCommand callers keep their existing Job script. */
347
+ returnLogs?: boolean;
318
348
  pool?: string;
319
349
  skills?: readonly string[];
320
350
  llm?: 'grok';
321
351
  clone?: boolean;
322
352
  hostMirror?: string;
323
353
  bunCache?: string;
354
+ /** GATE-INSTALL-CACHE — hostPath of the node-wide install tokens (absolute). */
355
+ installSlots?: string;
324
356
  /** 컨테이너 메모리 한도(기본 16Gi · lite 면 2Gi). */
325
357
  memoryLimit?: string;
326
358
  /** GATE-SPEED A3①: CPU request/limit override — only the release gate passes it. Omitted = request 1 / limit 4. */
@@ -345,6 +377,8 @@ export interface RunPodCommandOptions {
345
377
  readSkillEnv?: (skills: readonly string[]) => Record<string, string>;
346
378
  grokCredentials?: () => { grokAuth?: string; grokApiKey?: string; ghToken: string };
347
379
  artifactsRoot?: string;
380
+ /** Test seam for the launching instance's log store. */
381
+ logStore?: LogStore | null;
348
382
  sleep?: (ms: number) => Promise<void>;
349
383
  log?: (category: string, event: string, data: Record<string, unknown>) => void;
350
384
  env?: NodeJS.ProcessEnv;
@@ -410,6 +444,8 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
410
444
  if (hostMirror && !isAbsolute(hostMirror)) throw new Error('pod.hostMirror must be an absolute directory path');
411
445
  const bunCache = options.bunCache?.trim() || undefined;
412
446
  if (bunCache && !isAbsolute(bunCache)) throw new Error('pod.bunCache must be an absolute directory path');
447
+ const installSlots = options.installSlots?.trim() || undefined;
448
+ if (installSlots && !isAbsolute(installSlots)) throw new Error('pod installSlots must be an absolute directory path');
413
449
  // Fail before any Secret/Job is applied — a bad quantity must not reach the cluster.
414
450
  if (options.cpu) resolvePodCpu(options.cpu);
415
451
 
@@ -440,7 +476,8 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
440
476
  const context = member?.context ?? (currentContext?.status === 0 ? currentContext.stdout.trim() : '');
441
477
  const kubectl: Kubectl = (args, stdin) => baseKubectl(context ? ['--context', context, ...args] : [...args], stdin);
442
478
  const cleanupSecret = () => { kubectl(['-n', namespace, 'delete', 'secret', '-l', own, '--ignore-not-found']); };
443
- const artifactsDir = `${options.artifactsRoot ?? `${effectiveInstanceRoot()}/pod-artifacts`}/${name}`;
479
+ const artifactRoot = options.artifactsRoot ?? `${effectiveInstanceRoot()}/pod-artifacts`;
480
+ const artifactsDir = `${artifactRoot}/${name}`;
444
481
  try {
445
482
  if (!context && !options.kubectl) throw new Error('pod command: kubectl context 를 확인할 수 없다');
446
483
  let grokAuth: string | undefined;
@@ -485,8 +522,8 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
485
522
  const manifest = podCommandJobManifest({
486
523
  name, namespace, launch, image: imageRef ?? image,
487
524
  ...(imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}),
488
- repoUrl, command, skills, ...(llm ? { llm } : {}), ...(options.clone ? { clone: true } : {}),
489
- ...(options.source ? { source: options.source } : {}), ...(hostMirror ? { hostMirror } : {}), ...(bunCache ? { bunCache } : {}), ...(memoryLimit ? { memoryLimit } : {}), ...(options.cpu ? { cpu: options.cpu } : {}), deadlineSeconds,
525
+ repoUrl, command, skills, ...(options.returnLogs ? { returnLogs: true } : {}), ...(llm ? { llm } : {}), ...(options.clone ? { clone: true } : {}),
526
+ ...(options.source ? { source: options.source } : {}), ...(hostMirror ? { hostMirror } : {}), ...(bunCache ? { bunCache } : {}), ...(installSlots ? { installSlots } : {}), ...(memoryLimit ? { memoryLimit } : {}), ...(options.cpu ? { cpu: options.cpu } : {}), deadlineSeconds,
490
527
  ...(options.runId ? { runId: options.runId } : {}),
491
528
  });
492
529
  kubectl(['-n', namespace, 'delete', 'job', '-l', own, '--ignore-not-found']);
@@ -505,19 +542,31 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
505
542
  const logsResult = kubectl(['-n', namespace, 'logs', `job/${name}`, '-c', 'child']);
506
543
  const logsFailed = logsResult.status !== 0;
507
544
  const logs = logsFailed ? '' : logsResult.stdout;
508
- const artifactDir = options.artifactsRoot ?? `${effectiveInstanceRoot()}/pod-artifacts`;
545
+ const artifactDir = artifactRoot;
509
546
  let artifacts: PodArtifactCollection;
510
547
  if (logsFailed) {
511
548
  const reason = logsResult.stderr.trim() || `kubectl logs exit ${logsResult.status}`;
512
549
  artifacts = { files: null, names: [], error: reason };
513
550
  log('pod.command', 'artifact-collect-failed', { job: name, reason });
514
551
  } else {
552
+ const store = options.returnLogs ? (options.logStore === undefined ? getDefaultLogStore() : options.logStore) : null;
515
553
  collectPodArtifacts(logs, {
516
- dir: artifactDir,
517
- job: name,
518
- log: (c, e, d) => log(c, e, d),
554
+ dir: artifactDir, job: name, log: (c, e, d) => log(c, e, d),
555
+ ...(options.returnLogs ? { logStore: store } : {}),
519
556
  });
520
557
  const names = artifactNames(logs);
558
+ // A plain command need not write JSONL. Make its child output queryable without changing the store schema.
559
+ if (options.returnLogs && store) {
560
+ try {
561
+ const output = logs.split(/\r?\n/).filter((line) => !line.startsWith('ELANOUS_POD_ARTIFACT ')).join('\n').trim();
562
+ if (output) store.insertPodBatch(name, [{ line: -1, surface: 'pod', rec: {
563
+ ts: new Date().toISOString(), category: 'pod.command', event: 'child-output', level: 'info',
564
+ data: { job: name, output: tailBytes(output, options.childLogMaxBytes ?? podChildLogMaxBytes()), origin: 'pod', podJob: name },
565
+ } }]);
566
+ } catch (error) {
567
+ log('pod.command', 'child-output-import-failed', { job: name, reason: error instanceof Error ? error.message : String(error) });
568
+ }
569
+ }
521
570
  artifacts = { files: names.length, names, error: null };
522
571
  }
523
572
  // 표지가 없어도 알리는 경로는 실제로 존재해야 한다. 자식 로그 전문은 child.log (상한 넘으면 끝부분).
@@ -1,3 +1,7 @@
1
+ /** STOP-RECORD stop_class values used by the card terminal classifier. */
2
+ export const STOP_CLASS_POD_FAILURE = 'pod-failure' as const;
3
+ export const STOP_CLASS_NO_LAUNCH = 'no-launch' as const;
4
+
1
5
  /** Executor kinds of the dev child's `--json` result line (`src/index.ts` writeStdoutJson · DevExecutor.kind). */
2
6
  const CHILD_RESULT_KINDS = new Set(['self', 'external']);
3
7
 
@@ -0,0 +1,123 @@
1
+ // GATE-IMAGE-DEPS (0.2.21 · GATE-SPEED) — the release-gate shards' dependencies come baked in the harness image.
2
+ //
3
+ // Measured on the 0.2.20 cut gate (~/.elanous/release/0.2.20/gate-logs/cut · 24 root shards · pod-<n>.log ⊕ .junit.xml):
4
+ // install-slot wait median 156 s (max 509 s) ⊕ `bun install` root+pwa median 104 s (max 160 s) = median ≈ 260 s of a
5
+ // ≈ 1200 s shard spent before the first test — and the shards that waited longest were the ones the 1200 s deadline cut
6
+ // (pod-1 · 3 · 11 · 21 · 22 waited 327–509 s and ran only 215–436 s of tests). Alone the install takes seconds; 24 at
7
+ // once thrash the node (GATE-INSTALL-CACHE). Not installing at all removes both the wait and the install.
8
+ //
9
+ // How: `docker/harness/build.sh` stages each install tree's lockfile ⊕ the install-relevant projection of its package.json
10
+ // ({@link GATE_DEPS_PROJECTION_JS}) into `gate-deps/`; the Dockerfile installs them once into {@link GATE_DEPS_DIR}
11
+ // (`--frozen-lockfile`). The layer's inputs change only when dependencies change, so the per-commit rebuild reuses it from
12
+ // the build cache. A shard links `node_modules` to the baked tree only when its own lockfile and projection are
13
+ // byte-identical to the baked ones; anything else — no baked tree (old image), a different lockfile, a different
14
+ // projection, an existing node_modules, a failed link — falls back to the old path (install slot ⊕ `bun install`).
15
+ // The link is a symlink: copying (or hard-linking across overlay layers) is the very I/O the install slots were throttling.
16
+ // Module resolution follows the real path, and the baked layout mirrors the repo (`<dir>/node_modules`,
17
+ // `<dir>/apps/pwa/node_modules`), so the parent lookup from a package inside the pwa tree is the same as before.
18
+
19
+ import { createHash } from 'node:crypto';
20
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
21
+ import { join } from 'node:path';
22
+
23
+ /** Baked dependency root inside the full harness image (mirrors the repo layout). */
24
+ export const GATE_DEPS_DIR = '/opt/elanous-gate-deps';
25
+ /** The install trees, relative to the repo root, in install order (same as the old `bun install && (cd apps/pwa && bun install)`). */
26
+ export const GATE_DEPS_TREES = ['.', 'apps/pwa'] as const;
27
+
28
+ /**
29
+ * The part of a package.json that decides what `bun install` produces: dependency maps, overrides, trusted/patched
30
+ * dependencies and the install lifecycle scripts. `version`, `description` and every other script are dropped, so a
31
+ * release bump or a new script does not invalidate the baked layer. Run as `bun -e <this> <package.json>` (stdout).
32
+ * ⛔ build.sh (bake) and the shard (check) run this same text — changing it simply makes the next shards fall back once.
33
+ */
34
+ export const GATE_DEPS_PROJECTION_JS = [
35
+ "const p=JSON.parse(require('fs').readFileSync(process.argv[1],'utf8'));const o={};",
36
+ "for(const k of ['name','dependencies','devDependencies','optionalDependencies','peerDependencies','trustedDependencies','overrides','resolutions','patchedDependencies','workspaces'])if(p[k]!==undefined)o[k]=p[k];",
37
+ "const s={};for(const k of ['preinstall','install','postinstall','prepare'])if(p.scripts&&p.scripts[k]!==undefined)s[k]=p.scripts[k];",
38
+ "if(Object.keys(s).length)o.scripts=s;process.stdout.write(JSON.stringify(o,null,2)+'\\n');",
39
+ ].join('');
40
+
41
+ /** Same projection in-process (tests · build staging). */
42
+ export function projectDepsManifest(manifest: Record<string, unknown>): Record<string, unknown> {
43
+ const out: Record<string, unknown> = {};
44
+ for (const key of ['name', 'dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies', 'trustedDependencies', 'overrides', 'resolutions', 'patchedDependencies', 'workspaces']) {
45
+ if (manifest[key] !== undefined) out[key] = manifest[key];
46
+ }
47
+ const scripts = manifest.scripts as Record<string, unknown> | undefined;
48
+ const lifecycle: Record<string, unknown> = {};
49
+ for (const key of ['preinstall', 'install', 'postinstall', 'prepare']) if (scripts && scripts[key] !== undefined) lifecycle[key] = scripts[key];
50
+ if (Object.keys(lifecycle).length) out.scripts = lifecycle;
51
+ return out;
52
+ }
53
+
54
+ /**
55
+ * build.sh staging: `<dest>/<tree>/bun.lock` (verbatim) ⊕ `<dest>/<tree>/package.json` (the projection) for every install
56
+ * tree. The digest (sha256 of the staged bytes, 12 hex) becomes the image label `elanous.gate-deps`.
57
+ */
58
+ export function stageGateDeps(root: string, dest: string): { digest: string; trees: string[] } {
59
+ const hash = createHash('sha256');
60
+ const trees: string[] = [];
61
+ mkdirSync(dest, { recursive: true });
62
+ for (const tree of GATE_DEPS_TREES) {
63
+ const lock = join(root, tree, 'bun.lock');
64
+ const manifest = join(root, tree, 'package.json');
65
+ if (!existsSync(lock) || !existsSync(manifest)) continue;
66
+ const out = join(dest, tree);
67
+ mkdirSync(out, { recursive: true });
68
+ const lockBytes = readFileSync(lock);
69
+ const projected = `${JSON.stringify(projectDepsManifest(JSON.parse(readFileSync(manifest, 'utf8')) as Record<string, unknown>), null, 2)}\n`;
70
+ writeFileSync(join(out, 'bun.lock'), lockBytes, { mode: 0o644 });
71
+ writeFileSync(join(out, 'package.json'), projected, { mode: 0o644 });
72
+ hash.update(`${tree}\0`).update(lockBytes).update('\0').update(projected).update('\0');
73
+ trees.push(tree);
74
+ }
75
+ return { digest: hash.digest('hex').slice(0, 12), trees };
76
+ }
77
+
78
+ const sq = (value: string) => `'${value.replace(/'/g, `'\\''`)}'`;
79
+
80
+ /**
81
+ * Bash that installs the shard's dependencies (cwd = the repo clone) and sets `irc` (0 = ready). Each tree is linked to
82
+ * the baked tree when it matches, else installed — only the installs take an install slot (`install_slot_acquire` /
83
+ * `install_slot_release` from `installSlotScript` must already be defined when `installSlots > 0`). Every decision is a
84
+ * `[gate] deps <tree> baked|miss (<why>)` line in `log` (the shard's install.log).
85
+ */
86
+ export function gateDepsInstallScript(o: { log: string; installSlots: number; depsDir?: string }): string {
87
+ const deps = sq(o.depsDir ?? GATE_DEPS_DIR);
88
+ const log = o.log;
89
+ return [
90
+ 'gate_deps_link() {',
91
+ ` local t="$1" d=${deps} want`,
92
+ ' [ "$t" = . ] || d="$d/$t"',
93
+ ' if [ ! -d "$d/node_modules" ]; then echo "[gate] deps $t miss (no baked deps)"; return 1; fi',
94
+ ' if [ -e "$t/node_modules" ] || [ -L "$t/node_modules" ]; then echo "[gate] deps $t miss (node_modules exists)"; return 1; fi',
95
+ ' if ! cmp -s "$t/bun.lock" "$d/bun.lock"; then echo "[gate] deps $t miss (bun.lock differs)"; return 1; fi',
96
+ ` want=$(bun -e ${sq(GATE_DEPS_PROJECTION_JS)} "$t/package.json" 2>/dev/null) || { echo "[gate] deps $t miss (package.json unreadable)"; return 1; }`,
97
+ ' if [ "$want" != "$(cat "$d/package.json" 2>/dev/null)" ]; then echo "[gate] deps $t miss (package.json differs)"; return 1; fi',
98
+ ' if ! ln -s "$d/node_modules" "$t/node_modules"; then echo "[gate] deps $t miss (link failed)"; return 1; fi',
99
+ ' echo "[gate] deps $t baked"',
100
+ '}',
101
+ 'irc=0; gate_deps_need=',
102
+ `for t in ${GATE_DEPS_TREES.join(' ')}; do gate_deps_link "$t" >> ${log} 2>&1 || gate_deps_need="$gate_deps_need $t"; done`,
103
+ 'if [ -n "$gate_deps_need" ]; then',
104
+ ...(o.installSlots > 0 ? [` install_slot_acquire >> ${log} 2>&1`] : []),
105
+ ` for t in $gate_deps_need; do (cd "$t" && bun install) >> ${log} 2>&1; irc=$?; [ "$irc" -eq 0 ] || break; done`,
106
+ ...(o.installSlots > 0 ? [' install_slot_release'] : []),
107
+ 'fi',
108
+ ].join('\n');
109
+ }
110
+
111
+ /** Per-tree decisions from a shard's install log (`baked` · `miss:<why>`), for the gate's `pod-shard` observation. */
112
+ export function parseGateDeps(log: string): Record<string, string> {
113
+ const out: Record<string, string> = {};
114
+ for (const match of log.matchAll(/^\[gate\] deps (\S+) (baked|miss \(([^)]*)\))$/gm)) out[match[1]!] = match[2] === 'baked' ? 'baked' : `miss:${match[3]}`;
115
+ return out;
116
+ }
117
+
118
+ if (import.meta.main) {
119
+ const [cmd, dest] = process.argv.slice(2);
120
+ if (cmd !== 'stage' || !dest) { console.error('usage: pod-gate-deps.ts stage <dir>'); process.exit(2); }
121
+ const staged = stageGateDeps(process.cwd(), dest);
122
+ console.log(`digest=${staged.digest} trees=${staged.trees.join(',') || '-'}`);
123
+ }
@@ -0,0 +1,89 @@
1
+ // GATE-INSTALL-CACHE (0.2.20) — a node-wide throttle for the release-gate shards' `bun install`.
2
+ //
3
+ // Cause, measured on the 0.2.19 gate (`elanous logs --category release-loop.gate --event pod-shard --all --include-test`):
4
+ // · no cache is «not» the cause — the harness image carries a warm Bun cache, and a Pod that installed alone
5
+ // (10-07 10:48–10:55 · 12:13 · 12:16) took 2.4–5.0 s with `pod-bun-cache source=none`;
6
+ // · the same install took 150–340 s when 24 shard Pods (or ~20 isolation Pods) started together on the one 32-CPU
7
+ // node-b node (09:3x round 1: 221–337 s · 11:4x round 3: 150–240 s · 10:34 isolation wave: 124–188 s).
8
+ // ⇒ concurrent installs thrash the node (24 × copying node_modules out of the image cache + CPU), so a shared
9
+ // hostPath Bun cache would not help (the bytes are already local) — fewer installs «at once» does.
10
+ //
11
+ // Concurrency safety: the hostPath holds only throttle tokens (`slot-<n>` directories), never packages. Each Pod still
12
+ // installs into its own container (its own node_modules, its own image-local Bun cache), so correctness never depends on
13
+ // the token: a lost token (Pod killed mid-install) only lowers throughput until it goes stale (`staleSeconds`), a doubled
14
+ // token (two waiters reclaiming one stale slot in the same instant) only lets one extra install run, and a waiter that
15
+ // cannot get a token within `waitSeconds` — or finds the directory unwritable — installs anyway (fail-open).
16
+ // `mkdir` is atomic on one filesystem, and every Pod on a node sees the same hostPath directory.
17
+
18
+ /** Default hostPath of the install tokens on each gate node. */
19
+ export const POD_INSTALL_SLOTS_HOST_PATH = '/var/lib/elanous/gate-install-slots';
20
+ /** Default concurrent installs per node (`release.loop.gatePodInstallSlots` · 0 = off). Alone ≈ 3–5 s ⇒ 24 shards ≈ 6 waves. */
21
+ export const POD_INSTALL_SLOTS_DEFAULT = 4;
22
+ export const POD_INSTALL_SLOTS_MOUNT = '/gate-install-slots';
23
+
24
+ /** hostPath volume ⊕ mount for the tokens. `DirectoryOrCreate` makes a root-owned 0755 directory, so the Pod also needs
25
+ * {@link podHostDirsInit} (the child runs as uid 1000). */
26
+ export function podInstallSlotsVolume(hostPath: string = POD_INSTALL_SLOTS_HOST_PATH) {
27
+ return {
28
+ volume: { name: 'install-slots', hostPath: { path: hostPath, type: 'DirectoryOrCreate' } },
29
+ volumeMount: { name: 'install-slots', mountPath: POD_INSTALL_SLOTS_MOUNT, readOnly: false },
30
+ };
31
+ }
32
+
33
+ /**
34
+ * A root init container that hands the mounted hostPath directories to the child's uid. Without it a `DirectoryOrCreate`
35
+ * hostPath stays root:root 0755 and the child's `[ -w … ]` guard silently skips it (the Bun cache option was a no-op).
36
+ */
37
+ export function podHostDirsInit(image: string, imagePullPolicy: string, mounts: ReadonlyArray<{ name: string; mountPath: string; readOnly: boolean }>) {
38
+ const paths = mounts.map((mount) => `'${mount.mountPath.replace(/'/g, `'\\''`)}'`).join(' ');
39
+ return {
40
+ name: 'host-dirs', image, imagePullPolicy,
41
+ securityContext: { runAsUser: 0 },
42
+ command: ['sh', '-c'],
43
+ // Never fails the Pod: an unchanged directory only means the child falls back (no cache · no throttle).
44
+ args: [`for d in ${paths}; do mkdir -p "$d" && chown 1000:1000 "$d" && chmod 0775 "$d" || echo "[host-dirs] could not prepare $d"; done; exit 0`],
45
+ volumeMounts: mounts.map((mount) => ({ ...mount })),
46
+ };
47
+ }
48
+
49
+ const int = (value: number, label: string) => {
50
+ if (!Number.isSafeInteger(value) || value < 1) throw new Error(`invalid ${label}: ${value}`);
51
+ return value;
52
+ };
53
+
54
+ /**
55
+ * Bash that defines `install_slot_acquire` / `install_slot_release`. Acquire waits for one of `slots` tokens under `dir`
56
+ * (polling every `pollSeconds`, default 2 s), reclaims a token older than `staleSeconds` (minute granularity), and gives up after `waitSeconds` (install runs anyway).
57
+ * It prints `[gate] install-slot <n> waited <s>s` or `[gate] install-slot none …` for the shard log.
58
+ */
59
+ export function installSlotScript(o: { dir?: string; slots: number; waitSeconds?: number; staleSeconds?: number; pollSeconds?: number }): string {
60
+ const dir = `'${(o.dir ?? POD_INSTALL_SLOTS_MOUNT).replace(/'/g, `'\\''`)}'`;
61
+ const slots = int(o.slots, 'install slots');
62
+ const wait = int(o.waitSeconds ?? 600, 'install slot wait');
63
+ const poll = o.pollSeconds ?? 2;
64
+ if (!Number.isFinite(poll) || poll <= 0 || poll > 60) throw new Error(`invalid install slot poll: ${poll}`);
65
+ const staleMinutes = Math.max(1, Math.ceil(int(o.staleSeconds ?? 900, 'install slot stale age') / 60));
66
+ return [
67
+ 'install_slot=',
68
+ 'install_slot_acquire() {',
69
+ ` local d=${dir} t0 now n`,
70
+ ' t0=$(date +%s)',
71
+ ' if [ ! -d "$d" ] || [ ! -w "$d" ]; then echo "[gate] install-slot none (directory not writable)"; return 0; fi',
72
+ ' while :; do',
73
+ ` for n in $(seq 0 ${slots - 1}); do`,
74
+ ' if mkdir "$d/slot-$n" 2>/dev/null; then',
75
+ ' install_slot="$d/slot-$n"',
76
+ ' echo "[gate] install-slot $n waited $(( $(date +%s) - t0 ))s"; return 0',
77
+ ' fi',
78
+ // A token whose holder died (directory mtime older than the stale age · `find -mmin` is the same on GNU and BSD) is
79
+ // renamed away first (rename is atomic: one reclaimer wins) and the slot is tried again on the next pass.
80
+ ` if [ -n "$(find "$d/slot-$n" -maxdepth 0 -mmin +${staleMinutes} 2>/dev/null)" ]; then mv "$d/slot-$n" "$d/stale-$n-$$" 2>/dev/null && rm -rf "$d/stale-$n-$$"; fi`,
81
+ ' done',
82
+ ' now=$(date +%s)',
83
+ ` if [ $(( now - t0 )) -ge ${wait} ]; then echo "[gate] install-slot none (waited $(( now - t0 ))s)"; return 0; fi`,
84
+ ` sleep ${poll}`,
85
+ ' done',
86
+ '}',
87
+ 'install_slot_release() { if [ -n "$install_slot" ]; then rm -rf "$install_slot"; install_slot=; fi; }',
88
+ ].join('\n');
89
+ }
@@ -52,6 +52,18 @@ export interface PodLeaseMember {
52
52
  */
53
53
  harnessUsageBytes?: number[] | null;
54
54
  usageReason?: string | null;
55
+ /** GATE-RESERVE-AUTO — non-terminal release-gate shard Jobs (`gate-<uuid>`, kind=command). */
56
+ gateJobs?: number | null;
57
+ /** GATE-RESERVE-AUTO — of those, Jobs whose Pod is not bound to a node yet (not created, or Pending unscheduled). */
58
+ gateUnboundJobs?: number | null;
59
+ /** GATE-RESERVE-AUTO — CPU requested by the non-terminal gate Jobs' Pod templates (observability; Infinity: unreadable). */
60
+ gateCpuMillicores?: number | null;
61
+ /** GATE-RESERVE-AUTO — allocatable CPU of the schedulable (Ready, untainted, cordon-free) nodes. */
62
+ schedulableCpuMillicores?: number | null;
63
+ /** GATE-RESERVE-AUTO — per schedulable node: allocatable CPU − requests of every non-terminal Pod bound to it (gate Pods too). */
64
+ cpuFreeByNodeMillicores?: number[] | null;
65
+ /** GATE-RESERVE-AUTO — CPU requested by harness Pods still waiting for a node (they take CPU somewhere once bound). */
66
+ pendingHarnessCpuMillicores?: number | null;
55
67
  /** Pool-wide harness reservation: Σ min(limit, max(request, usage×1.5)), or max(request, limit) when the Pod's usage is unmeasured. */
56
68
  memoryReservedBytes?: number | null;
57
69
  /** Parallel to availableMemoryByNodeBytes: each schedulable node's allocatable memory. */
@@ -138,16 +150,19 @@ function object(value: unknown): Record<string, unknown> | null {
138
150
  return value !== null && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : null;
139
151
  }
140
152
 
141
- /** Effective scheduling reservation: max(sum of app requests, largest init request, Pod-level request) + overhead. */
142
- function requestedMemory(spec: Record<string, unknown>): number | null {
153
+ /**
154
+ * Effective scheduling reservation: max(sum of app requests, largest init request, Pod-level request) + overhead.
155
+ * A container without a request reserves its limit (Kubernetes defaults the request to the limit), or 0 with neither.
156
+ */
157
+ function requestedResource(spec: Record<string, unknown>, unit: 'memory' | 'cpu'): number | null {
143
158
  const request = (resources: unknown): number | null => {
144
159
  const r = object(resources);
145
160
  if (resources !== undefined && !r) return null;
146
161
  const requests = object(r?.requests);
147
162
  const limits = object(r?.limits);
148
163
  if ((r?.requests !== undefined && !requests) || (r?.limits !== undefined && !limits)) return null;
149
- const raw = requests?.memory ?? limits?.memory;
150
- return raw === undefined ? 0 : typeof raw === 'string' ? quantity(raw, 'memory') : null;
164
+ const raw = requests?.[unit] ?? limits?.[unit];
165
+ return raw === undefined ? 0 : typeof raw === 'string' ? quantity(raw, unit) : null;
151
166
  };
152
167
  const containers = spec.containers;
153
168
  if (!Array.isArray(containers) || !containers.length) return null;
@@ -167,12 +182,16 @@ function requestedMemory(spec: Record<string, unknown>): number | null {
167
182
  init = Math.max(init, v);
168
183
  }
169
184
  const pod = spec.resources === undefined ? 0 : request(spec.resources);
170
- const overheadRaw = object(spec.overhead)?.memory;
185
+ const overheadRaw = object(spec.overhead)?.[unit];
171
186
  const overhead = spec.overhead !== undefined && !object(spec.overhead) ? null
172
- : overheadRaw === undefined ? 0 : typeof overheadRaw === 'string' ? quantity(overheadRaw, 'memory') : null;
187
+ : overheadRaw === undefined ? 0 : typeof overheadRaw === 'string' ? quantity(overheadRaw, unit) : null;
173
188
  return pod === null || overhead === null ? null : Math.max(app, init, pod) + overhead;
174
189
  }
175
190
 
191
+ const requestedMemory = (spec: Record<string, unknown>): number | null => requestedResource(spec, 'memory');
192
+ /** GATE-RESERVE-AUTO — the CPU the scheduler reserves for a Pod (same rule as memory). */
193
+ const requestedCpu = (spec: Record<string, unknown>): number | null => requestedResource(spec, 'cpu');
194
+
176
195
  function runningLimit(spec: Record<string, unknown>): number | null {
177
196
  if (!Array.isArray(spec.containers) || !spec.containers.length) return null;
178
197
  let total = 0;
@@ -325,6 +344,8 @@ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kube
325
344
  };
326
345
  const nodeItems = parsedItems(nodes);
327
346
  const nodeFree = new Map<string, number>();
347
+ let schedulableCpu = 0;
348
+ const nodeCpuFree = new Map<string, number>();
328
349
  const nodeNames = new Set<string>();
329
350
  if (!nodeItems?.length) reasons.push(`cluster nodes: ${nodes.status === 0 ? 'invalid/empty response' : nodes.stderr.trim().split('\n').pop() || `rc=${nodes.status}`}`);
330
351
  else {
@@ -350,11 +371,11 @@ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kube
350
371
  }
351
372
  nodeNames.add(name);
352
373
  const repels = taintList.some((t) => t!.effect === 'NoSchedule' || t!.effect === 'NoExecute');
353
- if (spec?.unschedulable !== true && !repels && object(ready[0])?.status === 'True') { nodeFree.set(name, mem); nodeAllocatable.set(name, mem); }
374
+ if (spec?.unschedulable !== true && !repels && object(ready[0])?.status === 'True') { nodeFree.set(name, mem); nodeAllocatable.set(name, mem); nodeCpuFree.set(name, cores); schedulableCpu += cores; }
354
375
  memory += mem; cpu += cores;
355
376
  }
356
- if (valid) { result.allocatableMemoryBytes = memory; result.allocatableCpuMillicores = cpu; }
357
- else { nodeFree.clear(); nodeAllocatable.clear(); nodeNames.clear(); reasons.push('cluster nodes: invalid name/allocatable memory/cpu/readiness'); }
377
+ if (valid) { result.allocatableMemoryBytes = memory; result.allocatableCpuMillicores = cpu; result.schedulableCpuMillicores = schedulableCpu; }
378
+ else { nodeFree.clear(); nodeAllocatable.clear(); nodeCpuFree.clear(); nodeNames.clear(); reasons.push('cluster nodes: invalid name/allocatable memory/cpu/readiness'); }
358
379
  }
359
380
  // POD-ADMIT-BY-USAGE: every schedulable node needs a real-usage reading, or the member stays on limits.
360
381
  const unreadNode = podUsage && nodeUsage ? [...nodeFree.keys()].find((n) => !nodeUsage!.has(n)) : undefined;
@@ -366,6 +387,8 @@ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kube
366
387
  if (podItems && jobItems) {
367
388
  const jobNames = new Set<string>();
368
389
  const leasedJobs = new Set<string>();
390
+ const gateJobs = new Map<string, number>();
391
+ const boundGateJobs = new Set<string>();
369
392
  let jobsValid = true;
370
393
  for (const job of jobItems) {
371
394
  const meta = object(job.metadata);
@@ -373,12 +396,27 @@ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kube
373
396
  if (object(meta.labels)?.['elanous.substrate'] !== 'pod') continue;
374
397
  if (typeof meta.name !== 'string' || !meta.name) { jobsValid = false; continue; }
375
398
  jobNames.add(meta.name);
399
+ // GATE-RESERVE-AUTO: a release-gate shard Job (`gate-<uuid>`, kind=command) reserves its Pod template's CPU
400
+ // from creation until it is terminal — also while its Pod is still Pending or not created yet.
401
+ if (meta.name.startsWith('gate-') && object(meta.labels)?.['elanous.kind'] === 'command') {
402
+ const status = object(job.status);
403
+ const conditions = Array.isArray(status?.conditions) ? status.conditions as unknown[] : [];
404
+ const terminal = !!status?.completionTime || Number(status?.succeeded) > 0 ||
405
+ conditions.some((c) => (object(c)?.type === 'Failed' || object(c)?.type === 'Complete') && object(c)?.status === 'True');
406
+ if (!terminal) {
407
+ const spec = object(object(object(job.spec)?.template)?.spec);
408
+ // An unreadable request cannot be reserved as 0: it blocks harness admission until the gate ends.
409
+ gateJobs.set(meta.name, (spec ? requestedCpu(spec) : null) ?? Number.POSITIVE_INFINITY);
410
+ }
411
+ }
376
412
  if (object(meta.annotations)?.[POD_HOST_LEASE_ANNOTATION] === 'true') leasedJobs.add(meta.name);
377
413
  }
378
414
  if (!jobsValid) reasons.push('cluster jobs: missing name/labels');
379
415
  let running = 0, pending = 0, unleasedRunning = 0, limits = 0, reserved = 0, limitsValid = true, reservationsValid = nodeItems !== null && nodeNames.size === nodeItems.length;
380
416
  const pendingJobs = new Map<string, PendingJobIdentity>();
381
417
  let unknownPhase = false;
418
+ let pendingHarnessCpu = 0;
419
+ let cpuValid = true;
382
420
  const seenPods = new Set<string>();
383
421
  for (const pod of podItems) {
384
422
  const metadata = object(pod.metadata);
@@ -393,7 +431,9 @@ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kube
393
431
  if (seenPods.has(identity)) { reservationsValid = false; continue; }
394
432
  seenPods.add(identity);
395
433
  } else reservationsValid = false;
396
- const harness = metadata?.namespace === 'elanous-test' && labels?.['elanous.probe'] !== 'dns' &&
434
+ const ownerJob = String(labels?.['elanous.job'] ?? labels?.['job-name'] ?? '');
435
+ const gatePod = metadata?.namespace === 'elanous-test' && gateJobs.has(ownerJob);
436
+ const harness = metadata?.namespace === 'elanous-test' && labels?.['elanous.probe'] !== 'dns' && !gatePod &&
397
437
  (labels?.['elanous.substrate'] === 'pod' || jobNames.has(String(labels?.['elanous.job'] ?? '')));
398
438
  const spec = object(pod.spec);
399
439
  let limit: number | null = null;
@@ -425,6 +465,17 @@ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kube
425
465
  }
426
466
  // Pending Pods with no assigned node have no node reservation yet. They already occupy a pool slot.
427
467
  const nodeName = spec?.nodeName;
468
+ // GATE-RESERVE-AUTO: CPU is placed per node (a free core split across two nodes is not one slot). Every Pod
469
+ // bound to a schedulable node — gate Pods too — takes its request there; harness Pods without a node yet
470
+ // take CPU somewhere once bound. A gate Pod bound to any node marks its Job as placed.
471
+ const boundNode = typeof nodeName === 'string' && nodeName !== '' ? nodeName : null;
472
+ if (gatePod && boundNode !== null && nodeNames.has(boundNode)) boundGateJobs.add(ownerJob);
473
+ if ((boundNode !== null && nodeCpuFree.has(boundNode)) || (boundNode === null && harness && phase === 'Pending')) {
474
+ const cpu = spec ? requestedCpu(spec) : null;
475
+ if (cpu === null) cpuValid = false;
476
+ else if (boundNode !== null) nodeCpuFree.set(boundNode, nodeCpuFree.get(boundNode)! - cpu);
477
+ else pendingHarnessCpu += cpu;
478
+ }
428
479
  if ((nodeName === undefined || nodeName === '') && phase === 'Pending' && spec && typeof metadata?.namespace === 'string' &&
429
480
  requestedMemory(spec) !== null) continue;
430
481
  const requested = spec ? requestedMemory(spec) : null;
@@ -432,6 +483,15 @@ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kube
432
483
  if (nodeFree.has(nodeName)) nodeFree.set(nodeName, nodeFree.get(nodeName)! - Math.max(requested, reserve ?? limit ?? 0));
433
484
  }
434
485
  if (unknownPhase) reasons.push('cluster pods: missing/Unknown phase');
486
+ if (jobsValid && !unknownPhase && result.schedulableCpuMillicores !== undefined) {
487
+ result.gateJobs = gateJobs.size;
488
+ result.gateUnboundJobs = [...gateJobs.keys()].filter((job) => !boundGateJobs.has(job)).length;
489
+ result.gateCpuMillicores = [...gateJobs.values()].reduce((sum, cpu) => sum + cpu, 0);
490
+ }
491
+ if (cpuValid && !unknownPhase && result.schedulableCpuMillicores !== undefined) {
492
+ result.cpuFreeByNodeMillicores = [...nodeCpuFree.values()];
493
+ result.pendingHarnessCpuMillicores = pendingHarnessCpu;
494
+ }
435
495
  if (jobsValid && !unknownPhase) { result.running = running; result.pending = pending; result.pendingJobs = [...pendingJobs.values()]; result.unleasedRunning = unleasedRunning; }
436
496
  if (limitsValid && jobsValid && !unknownPhase) {
437
497
  result.memoryLimitBytes = limits;
@@ -464,6 +524,29 @@ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kube
464
524
  }) };
465
525
  }
466
526
 
527
+ /**
528
+ * GATE-RESERVE-AUTO — while a release-gate shard Job is not terminal, how many more harness Pods (each requesting
529
+ * `POD_CHILD_REQUESTS.cpu`) fit beside it. The gate gets its CPU first: while any gate shard is still waiting for a
530
+ * node, no harness Pod is admitted (the scheduler may place new harness Pods where the shard would have gone). Once
531
+ * every shard is bound its CPU sits on its node, and the answer is Σ per-node ⌊free ÷ request⌋ minus the harness Pods
532
+ * still waiting for a node. `undefined`: no gate Job is active (normal admission) · `null`: a gate is active but CPU
533
+ * could not be measured — a Pod's or a gate template's request is unreadable (admit nothing more). The count comes from this one reading, never from earlier ones.
534
+ */
535
+ export function harnessCpuSlotsBesideGate(measure: PoolLeaseMeasure): number | null | undefined {
536
+ if (!measure.members.some((m) => (m.gateJobs ?? 0) > 0)) return undefined;
537
+ const perPod = quantity(POD_CHILD_REQUESTS.cpu, 'cpu')!;
538
+ let slots = 0;
539
+ for (const m of measure.members) {
540
+ if (m.gateJobs == null || m.gateUnboundJobs == null || m.cpuFreeByNodeMillicores == null || m.pendingHarnessCpuMillicores == null) return null;
541
+ // A gate Job whose Pod template CPU cannot be read is unmeasured even when its Pod is bound (fail closed).
542
+ if (m.gateCpuMillicores == null || !Number.isFinite(m.gateCpuMillicores)) return null;
543
+ if (m.gateUnboundJobs > 0) return 0;
544
+ const placeable = m.cpuFreeByNodeMillicores.reduce((sum, free) => sum + Math.max(0, Math.floor(free / perPod)), 0);
545
+ slots += Math.max(0, placeable - Math.ceil(m.pendingHarnessCpuMillicores / perPod));
546
+ }
547
+ return slots;
548
+ }
549
+
467
550
  /** Per-node memory and per-member slots cannot be exchanged across nodes/clusters. */
468
551
  export function recommendConcurrency(measure: PoolLeaseMeasure, options: { capacity: number; perGoalMemory?: string; liteGoalMemory?: string; accounts: number; perAccount: number }): PoolLeaseRecommendation {
469
552
  const empty: PoolLeaseRecommendation = { recommended: null, limitedBy: null, reason: null, capacitySlots: null, memorySlots: null, liteMemorySlots: null, placeableSlots: null, accountSlots: null, running: null, pending: null, unleasedRunning: null };