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.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/MObU8ujkMciAKr6ljAV05/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/088d8e8a.8069543d082bec12.js +160 -0
- package/apps/pwa/out/_next/static/chunks/1109.78a7a3288c0259d9.js +148 -0
- package/apps/pwa/out/_next/static/chunks/115.1c6690b8604f81c4.js +32 -0
- package/apps/pwa/out/_next/static/chunks/119fc678.6f591cdb84ca5791.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1262.76a3f08e67c5f816.js +309 -0
- package/apps/pwa/out/_next/static/chunks/{4323.c5dfb6f503304775.js → 1268.0840ff91e083fcbd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1307-c97eba9fb72a9a5e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1358d292.5c87f12cad6eaa91.js +122 -0
- package/apps/pwa/out/_next/static/chunks/1438.27a1a7b8f2308004.js +149 -0
- package/apps/pwa/out/_next/static/chunks/1442.02d85cd88f4c1eac.js +79 -0
- package/apps/pwa/out/_next/static/chunks/1501-1aad662d82ba43f7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1584.9e2dd53da6e6d4b1.js +24 -0
- package/apps/pwa/out/_next/static/chunks/1586.b989b0614afb77e6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1609.2ad355062870151f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1612-f6df0fc0eed7157b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1923.37f1d5fe7ebe3860.js +27 -0
- package/apps/pwa/out/_next/static/chunks/1948.5f35b39eef222a94.js +5 -0
- package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1993.3fd074a2a246234e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2977-e38442e86289f1d3.js → 211-9c9090c59e2c5907.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2140.d2daf9ee96e295d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2212.0359851457473efc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/224.583e562365a52f74.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2328.0f37465646bafa7d.js +36 -0
- package/apps/pwa/out/_next/static/chunks/2372.bf71372184991937.js +286 -0
- package/apps/pwa/out/_next/static/chunks/2489-ff55a35b202c0f94.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2537-6c289376caa88ca1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2549-a8515c86f9e8cb96.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2655.72cc6a97276c5acc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2743-ef6e8c3ced74cce5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2840-5458ad50c49cac6a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2856.f75d5baef7f49b74.js +1 -0
- package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3344.0de4b0ec5a454b7a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/335.a13f339e97893437.js +94 -0
- package/apps/pwa/out/_next/static/chunks/3394.646348f34b2500be.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3443.6276909b53d72d53.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3495.abd4254d808bf2dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3522-1de1319d028ac89c.js +25 -0
- package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3721.ba5b82db0d9a5572.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3919.9ff7dc25afefee33.js +74 -0
- package/apps/pwa/out/_next/static/chunks/3998.bd9a69d9b50e9bd2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4003.ab6db1dc266576c9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4144-fad095b48a1862ee.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4216.e8ac94b9fe6b96ab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4327.c44dc2b55cb43ee5.js +34 -0
- package/apps/pwa/out/_next/static/chunks/4401.94257d497b6d26bc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4559.17ca2cdbaa8d414f.js +115 -0
- package/apps/pwa/out/_next/static/chunks/4565-3d7e3fdeb43686a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4588.ad3364cb7fb97ada.js +86 -0
- package/apps/pwa/out/_next/static/chunks/4706.97e734bd4c9816ca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4795-f372490b178583fc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4982.6e44352f751b9a1b.js +77 -0
- package/apps/pwa/out/_next/static/chunks/5342.49c5c5a530e62b50.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5360-08a80abdf3a4da99.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5431.66c58ab1327ee0b7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5717.bcfcb96e701b82e0.js +90 -0
- package/apps/pwa/out/_next/static/chunks/5755-30d102586789bf7f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5957.3f1a4012388dd962.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5977-59fedea50d13c762.js +1 -0
- package/apps/pwa/out/_next/static/chunks/602.9bf02ac8e9502598.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6020.3a1b347e2d2dfacc.js +8 -0
- package/apps/pwa/out/_next/static/chunks/{4847.56f5bc454fbcc827.js → 6110.1297d4093fdfbb0d.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/6158.566c890adc06a459.js +2 -0
- package/apps/pwa/out/_next/static/chunks/6375.fce1d174ba6d0324.js +64 -0
- package/apps/pwa/out/_next/static/chunks/6546.92a828aef441271f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/658.957fdb0a8c6c6c8e.js +287 -0
- package/apps/pwa/out/_next/static/chunks/6706.323590aabb8b1293.js +226 -0
- package/apps/pwa/out/_next/static/chunks/6747.b67947f293a4c02d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6767.8e6c5f6e744fcd9f.js +38 -0
- package/apps/pwa/out/_next/static/chunks/6868.000b24a3045c5574.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6946.bba74e9c96050f30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7033.a558f1b2f6912a53.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7065.46029c26c53eac50.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7087.6fae577d09249bc1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7175-350f4ce2873bc038.js +1 -0
- package/apps/pwa/out/_next/static/chunks/722.de8d9801e0f6d414.js +41 -0
- package/apps/pwa/out/_next/static/chunks/{1470.98a6ab61ccb4131c.js → 726.83532d69452ee24b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6062-73e68343a9814f6a.js → 7327-2d69fb321ba73389.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7497.57981e9d7a75a52a.js +24 -0
- package/apps/pwa/out/_next/static/chunks/7630.2b9a68659053ff4e.js +193 -0
- package/apps/pwa/out/_next/static/chunks/7672.4af122ad892117c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/771.6ab889a233e7dae7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7899.597af4a9996c907f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/79b78388.663188133449d707.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8096.f7da0776cfde6300.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8194.484f51e273d9abd2.js +217 -0
- package/apps/pwa/out/_next/static/chunks/8401-35e94e9091f5eb8d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8769.815ca69879f608b4.js +104 -0
- package/apps/pwa/out/_next/static/chunks/8857-f1762140ef9a96b1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/88729267.9c6c000157c83f2f.js +62 -0
- package/apps/pwa/out/_next/static/chunks/8f835b4f.048eeb02299e3e12.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9068.5f2764fccf86c1b2.js +93 -0
- package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9103-60fbef33e66f0a46.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9247-7581825d511a64c0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7647.2fcd30d894d5ee30.js → 935.4ddaa99fc1d1126c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9451.ebfb0dcc9c02250f.js +23 -0
- package/apps/pwa/out/_next/static/chunks/9497-b0e758d1cd4ae8d7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9559.e648369b1d57cf9a.js +62 -0
- package/apps/pwa/out/_next/static/chunks/9832.2ab2e818013574c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/a9f2b54d-40c16bd0efdb04b8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-937c92292a4ab4f0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-d781be3e3a9cfd74.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-b0a80997903852d8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-2b1263165630d9c8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-ea998f7f5d156bea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-33c328b64ca508f8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-e2d6bed48ac20120.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-111cabe4d1fd69ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-544dd4bd4847eeb7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-3ee191319bf97d87.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-e4bcb2c503dab04b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-300897dcbbd5ed76.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-a449f1ed472b81c2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-9045094d6d3b5cd0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-c06673596849ef45.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-103fa3191c1b2e08.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-9f81233f29dcd409.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/{page-b70cd005d9169e25.js → page-be8b4615c919e36c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-c685b104e0dfa609.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-8d693331cfbb6add.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-cef4f30a4c22fae1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66ea1b54fc83fa15.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-7a12f61be4a579d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-c85b0589591a99c9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-725a4da02dd129eb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-8cc707bd09a497d9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-90b55be1f15ce0e8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cb16606e08be65db.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-16dc64b74ed6a40a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-21bec4afeb2f0917.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-dc39ee7a36d26385.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-84ae759f76053672.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-3db58ddb61fe2be9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c5315e30f3528dc4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-2fe368efb6de3b6a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-8d04132dd0ac5e10.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-f23484de467c9ea3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-c69762987b751174.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-5456692bda39aafb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-d75f7cd65845ce98.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-5603f5e568b25ce3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-d2cc0f4e89f42028.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-a6eb73b8a4840f1c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-c0a3f4d10edb83fa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6c0b88068c143f15.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-7f8d7fcdf058a845.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-22163556cd38bc88.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-141436fd26dd56fc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/d70879db.c66c56a95a91efc6.js +66 -0
- package/apps/pwa/out/_next/static/chunks/{8046cadf-211ca3c0e00f2b72.js → f6231284-45726550aba08fca.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{2ea96958.e8c7e112de50b067.js → f6b7c9ba.64a895b8b2fedbdc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/f8626309.a4e5d362b1d55e59.js +1 -0
- package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-f2b4016b9b981bc8.js +1 -0
- package/apps/pwa/out/_next/static/css/{d3427996da11e480.css → b9d8151e48998417.css} +1 -1
- package/apps/pwa/out/_next/static/css/e81df11490b4413b.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +23 -23
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +23 -23
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +23 -23
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +22 -22
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +22 -22
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +23 -23
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +22 -22
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +23 -23
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +23 -23
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +22 -22
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +23 -23
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +24 -24
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +23 -23
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +23 -23
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +22 -22
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +23 -23
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +23 -23
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +23 -23
- package/apps/pwa/out/live-run/index.html +1 -0
- package/apps/pwa/out/live-run/index.txt +35 -0
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +22 -22
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +22 -22
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +22 -22
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +23 -23
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +23 -23
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +22 -22
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +22 -22
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +22 -22
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +22 -22
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +23 -23
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +23 -23
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +22 -22
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +23 -23
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +23 -23
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +23 -23
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +23 -23
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +23 -23
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +22 -22
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +23 -23
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +23 -23
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +23 -23
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +23 -23
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +23 -23
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +23 -23
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +23 -23
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +23 -23
- package/graphs/demo/editor-recipes.yaml +22 -0
- package/graphs/drafts/recipes.yaml +4 -4
- package/graphs/implement-loop.yaml +277 -5
- package/graphs/retro/daily.yaml +25 -0
- package/graphs/retro/recipes.yaml +24 -0
- package/package.json +1 -1
- package/scripts/ask-marker-check.ts +3 -1
- package/scripts/coord-channel-watch.sh +31 -4
- package/scripts/docs-lint.ts +4 -1
- package/scripts/land-global-checks.ts +89 -0
- package/scripts/lib/pch-regress-map.ts +2 -1
- package/scripts/marketing/generate-ax-brief.ts +18 -1
- package/scripts/measure-nl-routing-corpus.ts +11 -3
- package/scripts/release-loop/gate-node.ts +296 -25
- package/scripts/release-loop/gate-timing.ts +116 -7
- package/scripts/release-loop/preflight.ts +6 -4
- package/scripts/release-loop/publish-window.ts +141 -0
- package/scripts/release-loop/release-docs.ts +1 -4
- package/scripts/release-loop/release-universe.ts +38 -0
- package/scripts/release-loop/resume-release.ts +4 -1
- package/scripts/release-loop/shard-plan.ts +50 -5
- package/scripts/release-loop/unattended-release.ts +10 -2
- package/scripts/release-story/draft.ts +5 -1
- package/scripts/retro/shadow-compare.ts +71 -0
- package/scripts/rhythm/daily.ts +8 -2
- package/src/agent-mission/driver.ts +34 -6
- package/src/autopilot/build/llm-conflict-merge.ts +132 -7
- package/src/autopilot/build/merge-intent.ts +135 -7
- package/src/away/release-watch.ts +20 -3
- package/src/browser-cdp/client.ts +10 -5
- package/src/cli/config-drift.ts +513 -0
- package/src/cli/log-event-names.ts +4 -0
- package/src/cli/ops-cli.ts +11 -0
- package/src/cli/ops-overview-cli.ts +50 -0
- package/src/cli/pod-cli.ts +1 -0
- package/src/cli/pr-cli.ts +20 -0
- package/src/cli/release-cli.ts +20 -5
- package/src/cli/retro-cli.ts +78 -0
- package/src/cli/tasks-cli.ts +200 -8
- package/src/dashboard/index.ts +40 -4
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +5 -4
- package/src/dashboard/slash-runtime/help-from-registry.ts +57 -31
- package/src/domains/outbound-log-probe.ts +6 -1
- package/src/esc-abort-gate.ts +3 -1
- package/src/graph-runner/graph-cli.ts +57 -0
- package/src/graph-runner/graph-run-api.ts +218 -0
- package/src/graph-runner/runner.ts +11 -1
- package/src/graph-wizard/archetypes.ts +88 -0
- package/src/graph-wizard/generate.ts +545 -0
- package/src/graph-wizard/saved-steps.ts +120 -0
- package/src/graph-wizard/steps.ts +193 -0
- package/src/harness/harness-cli-command.ts +482 -59
- package/src/harness/harness-queue.ts +315 -25
- package/src/harness/harness-salvage-cli.ts +62 -0
- package/src/hq/fence-outcomes.ts +63 -0
- package/src/hq/fence-wrapper.ts +33 -0
- package/src/hq/hq.ts +11 -1
- package/src/index.ts +76 -10
- package/src/intake-plane/digest.ts +18 -11
- package/src/maturity/feature-maturity.ts +4 -3
- package/src/nexus/api/draft-metrics.ts +114 -0
- package/src/nexus/api/graph-kinds.ts +10 -4
- package/src/nexus/api/graph-wizard-api.ts +53 -0
- package/src/nexus/api/graphs-api.ts +220 -10
- package/src/nexus/api/harness-traversal.ts +205 -0
- package/src/nexus/api/http-server.ts +69 -3
- package/src/nexus/api/loop-edges.ts +4 -2
- package/src/nexus/api/ops-api.ts +22 -7
- package/src/nexus/api/ops-board-route.ts +48 -0
- package/src/nexus/outbound/router.ts +28 -8
- package/src/onboarding/entry-hints.ts +5 -0
- package/src/onboarding/first-run.ts +3 -0
- package/src/onboarding.ts +24 -6
- package/src/ops/overview-collect.ts +345 -0
- package/src/ops/overview-seat-era.ts +90 -0
- package/src/ops/overview.ts +223 -0
- package/src/release-loop/feature-store.ts +14 -11
- package/src/release-loop/gate-shards.ts +163 -0
- package/src/release-loop/release-schedule.ts +6 -2
- package/src/retro/daily.ts +372 -0
- package/src/seat-loop/op-decide.ts +173 -0
- package/src/seat-loop/seat-loop.ts +60 -3
- package/src/seat-loop/seat-report.ts +58 -3
- package/src/self-dev/ask-launch-flow.ts +9 -6
- package/src/self-dev/ask-launch-io.ts +52 -2
- package/src/self-dev/ask-path-candidates.ts +40 -2
- package/src/self-dev/draft-sweep.ts +371 -16
- package/src/self-dev/draft-triage-rules.ts +19 -4
- package/src/self-dev/graph-journey-nodes.ts +157 -0
- package/src/self-dev/graph-journey-route.ts +105 -0
- package/src/self-dev/launch-preflight.ts +43 -3
- package/src/self-dev/pr-preview.ts +84 -16
- package/src/self-dev/release-path-guard.ts +7 -4
- package/src/self-implement/blocked-draft-policy.ts +101 -0
- package/src/self-implement/execution-clarification.ts +11 -0
- package/src/self-implement/frozen-merges.ts +23 -0
- package/src/self-implement/gate-env-retry.ts +83 -0
- package/src/self-implement/goal-author.ts +14 -0
- package/src/self-implement/heal-intake.ts +3 -0
- package/src/self-implement/orchestrator.ts +234 -6
- package/src/self-implement/run-stop.ts +123 -0
- package/src/self-implement/run-ttl.ts +258 -0
- package/src/self-implement/salvage-retention.ts +123 -0
- package/src/self-implement/seams.ts +7 -0
- package/src/self-implement/self-implement-cli.ts +2 -0
- package/src/self-implement/self-implement-runtime.ts +12 -0
- package/src/self-implement/sibling-resync.ts +223 -0
- package/src/self-implement/stop-autoheal.ts +204 -0
- package/src/session-runtime/index.ts +5 -3
- package/src/shell-env-bootstrap.ts +17 -3
- package/src/task-agent/board.ts +1 -1
- package/src/task-agent/card-evidence.ts +105 -10
- package/src/task-agent/judge.ts +7 -0
- package/src/task-agent/live-moves.ts +120 -20
- package/src/task-agent/mission.ts +1 -1
- package/src/task-agent/parent-host.ts +315 -0
- package/src/task-agent/shadow.ts +10 -1
- package/src/task-agent/task-hand.ts +16 -4
- package/src/task-orchestrator/surfaces/pod-command-job.ts +60 -11
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +4 -0
- package/src/task-orchestrator/surfaces/pod-gate-deps.ts +123 -0
- package/src/task-orchestrator/surfaces/pod-install-slots.ts +89 -0
- package/src/task-orchestrator/surfaces/pod-lease.ts +93 -10
- package/src/task-orchestrator/surfaces/pod-pool.ts +147 -34
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +58 -9
- package/src/telegram-commands.ts +41 -0
- package/src/testing/real-cli-spawn.ts +106 -0
- package/src/tool-runtime/browser-runtime.ts +87 -3
- package/src/tool-runtime/index.ts +1 -0
- package/src/user-config.ts +76 -7
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/J5H7BvqgSI8DUjtRRcA_S/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1157-cba0e8553d35a980.js +0 -1
- package/apps/pwa/out/_next/static/chunks/171-23a877ed11720de9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2336-e830c777eb6bce04.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2375-e061bb24cd2b5cde.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3094.67339318c7f72a3a.js +0 -2
- package/apps/pwa/out/_next/static/chunks/338-ceb956612dcfaa86.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3395-835e4ceaa6335ba4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +0 -5
- package/apps/pwa/out/_next/static/chunks/3441-be82e3595aef3a20.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3689-89905039bf3e7257.js +0 -1
- package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3822-6f268bc26c6a84ac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4457-d7df81c90f9d0268.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4551-77dadae76f6cbace.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4978.6fd93b974441e87f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5012-f746f6549b869f91.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5791-bb82743fa6299562.js +0 -1
- package/apps/pwa/out/_next/static/chunks/62-5bfed1e23790b745.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6320-9bc8561fde416e07.js +0 -1
- package/apps/pwa/out/_next/static/chunks/649-ca96d1c5d09e02b8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6726-85a7db59b288c53f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6735-33267c4630d0d963.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7633-141682015873beb6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8067-143791231c99bd02.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8430-a10cfba8ef282d4e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8870-9614a213b990b10b.js +0 -25
- package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9454-12c2f30dea62291c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/976-4a9b4ebdca7c739a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9989-15cdf568bafa9fc8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/ae787e4c-e5b1ef00b2dbceca.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-deef5ee191830f14.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-b193da485f9a14b2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-b6e1a79df0e214f4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-e84df4d907f703c7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-a5fb9a5db89ae4ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-1499f481d7fc52d6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-c7cf753aeb648790.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-4118469e514e10fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-e968f7630654332a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-505915052f947022.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-c2f962fb4a3b06c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-7b5ed0bd8a589ec4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-cf8193250dd42dd4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-cf3f5dfa35dea8e9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-5b29d9b06ceadade.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-a15e7fcc283e9ca7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-d2915e7b70ff3d0e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-2595f0eba29f57e1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-c776322454156e36.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-77bbd17f4435aa10.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-049689e611ecc936.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-85954a4fb09c44eb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-e773e552e1068ee6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-5633c4bfa7684b4a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-d829b92bd01b4994.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-061c317fc771becf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-3a45ece8865ed4b6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-0be990618c0d56bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-bfea4865dc244b73.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-0d6ba8e337741484.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-76a88b55d816482e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-bba1542df59f61ce.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-4185c95689bb372e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b699e1a8d5d34b8b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-c225e338c31bf274.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-449353f70507ca1f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-90b889714102476d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-f1ee7fbeb7f96ddc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-d59a95e1434dfaa0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-71491a23414d1a47.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-de7112bdb65fa4fe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-feb4d0d88079660d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f17d887b40dd6b1d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-8da77be2192bcd5d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-72c3b9a5e9582b29.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3afe4c8d0c48c39.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-b9308f2d8a931531.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-6f35f9d892fa630b.js +0 -1
- package/apps/pwa/out/_next/static/css/85f53c6f35d11382.css +0 -1
- /package/apps/pwa/out/_next/static/{J5H7BvqgSI8DUjtRRcA_S → MObU8ujkMciAKr6ljAV05}/_ssgManifest.js +0 -0
- /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: [
|
|
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 ||
|
|
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
|
-
...
|
|
276
|
+
...hostDirMounts,
|
|
250
277
|
] } : {}),
|
|
251
278
|
}],
|
|
252
|
-
...(secretKeys || o.hostMirror ||
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
/**
|
|
142
|
-
|
|
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?.
|
|
150
|
-
return raw === undefined ? 0 : typeof raw === 'string' ? quantity(raw,
|
|
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)?.
|
|
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,
|
|
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
|
|
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 };
|