elanous 0.2.42 → 0.2.44
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/{X1ucLw10Cds_h614NoEnh → HMXsxdy5MIIcHPMHv1wD4}/_buildManifest.js +1 -1
- package/apps/pwa/out/_next/static/chunks/08334415.f0e80896acbef722.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1032-78ba4f6391258cab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1082-53f86a26e03d309d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1109-7d0e264f6f2edbc7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1124-7c26330402481143.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2160.a097e7b138ae1c55.js → 118.2a89982fe4276880.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1244.6c435dbf1bf21d8a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1252.9e665904d3aa3b1a.js +32 -0
- package/apps/pwa/out/_next/static/chunks/1266.a46cc33cb6a72d75.js +104 -0
- package/apps/pwa/out/_next/static/chunks/1306.967240ede9a0f757.js +24 -0
- package/apps/pwa/out/_next/static/chunks/1344.3b95f3bd1b77a3fe.js +94 -0
- package/apps/pwa/out/_next/static/chunks/{4043.6b91203436c9c86b.js → 1401.22dfe92640a2b1ac.js} +4 -4
- package/apps/pwa/out/_next/static/chunks/1431-f7c78bd5be21e011.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1451-cdd35a8a0b31834b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6632.0eac1c554f8f2ef3.js → 1694.18debfd77562e56d.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/1708-f03d587b7a7545c8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1775-fa7152bdeff6c95f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/178-4da509cd7266672a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1850-522de4ee34957210.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1876-c6f6e0ddcc76c6a1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1976.097066b072432242.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3507.97e1977bc4d5fb0a.js → 1978.eeb60a606dc5deac.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/2043.e233e3b5a3e790f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2150.71b94e3348107c83.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2158.ebc6fc5a10280ab7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2216.802962c04f40a26e.js +64 -0
- package/apps/pwa/out/_next/static/chunks/2294.7890edcfa046c97d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/235.c89aaae4bcd6233a.js +74 -0
- package/apps/pwa/out/_next/static/chunks/{5885.0a3e52c8faccec98.js → 239.de075467b5523469.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/249.ba7051590d603c8f.js +226 -0
- package/apps/pwa/out/_next/static/chunks/2571.a6ac4e26c8618403.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2610.e149cd564a08913e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2705.9816f3c244f8b85f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2801.afeb8a4dae22f85f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2954.bbefc95d6b20e0d8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2e0f720a.a0697c08e3806a60.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3042.96ec235fe41c8d4e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3097-8e358f158acef46b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3205-93b1abe28b4fb16a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3273.b4434f0dcae7eb29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3311.9cbc43ea0c90d60c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1487.2f67b79444062da6.js → 3349.b5f09ee474d9533e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3503.a47a9755fb4d97b0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3506-5f429b3b7e1392b0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3843.bad404d008893291.js +77 -0
- package/apps/pwa/out/_next/static/chunks/3877-4b40e530b47e3bf9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/435-ce2958253cf7de61.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4393.28fa5228d0eec217.js +149 -0
- package/apps/pwa/out/_next/static/chunks/4404.7281c847c6e10dfc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8673-bab689a55f7e13b9.js → 4415-b9d6a90960920a4a.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/447-5b304de1fe098f43.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4484-8ebea83d431e6fae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8262.ddc03e06b733f184.js → 4502.bdcc877abd437a05.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4599-f95790fc5c65b8df.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1262.e804feb8fd1acb8c.js → 46.57c783fa4884714f.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/463-b9114e411f33728e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4659.a57d1f0c5d754cd2.js +36 -0
- package/apps/pwa/out/_next/static/chunks/4722-a847e7a28115035f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1063.2fb8a9dc55c731f2.js → 4757.67c6d7473a038e78.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4874-c86f878ec4502ad3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6377.bc7df239edbff244.js → 4891.3f80ba517a3b2373.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4998.810ca8833084caa9.js +62 -0
- package/apps/pwa/out/_next/static/chunks/4f98febc.ea5841d4d616bfb2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3482.a9ae9d2ad6a5bd97.js → 5052.7dbb12a55d4f18f5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5166-ca73b3e361085a6d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5180-039f4a18b07ad387.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5232.5a2ce4daf7a9e11d.js +115 -0
- package/apps/pwa/out/_next/static/chunks/5439.3fec6dbce29ecb36.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5612-074d9a5e00dfac6a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5675-01989d524a84c505.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5751.af641a1adf5ec697.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5758-c6cb98714d18ab0c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/578eded8.b582637306e16964.js +122 -0
- package/apps/pwa/out/_next/static/chunks/5790.5eb06a653ebd8571.js +34 -0
- package/apps/pwa/out/_next/static/chunks/5853-4033e5bd5afbe06b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5904.97d70bb7f46b2db7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3347.f846266b5e5f3ede.js → 5989.7ecd362c5c44b747.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/{73d2eb0e.4966ad55293c801a.js → 5a34fb2f.52f1b4e1baebb48a.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/{7a98bc80.fa935989e829f9c2.js → 5a5078f8.2099200e5ea95e16.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6036-c302c34ff0b3d410.js +1 -0
- package/apps/pwa/out/_next/static/chunks/606.ef81c313381f4264.js +1 -0
- package/apps/pwa/out/_next/static/chunks/616.1af54a928b1ba704.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6201.bc936f71f613a8e9.js +41 -0
- package/apps/pwa/out/_next/static/chunks/6204-fdc86e0e64c4f92d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7411.9b4b8b747dc7b802.js → 6245.1182e007e9d230d9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{b450776b.3db34d8d965f82d3.js → 63699a35.17262e162bc514b2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6420.494801a80030160c.js +27 -0
- package/apps/pwa/out/_next/static/chunks/{e8543907.5b040a5f22a0b0e5.js → 6549dad0.2740b25e51bf8f05.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{9427.96aa912ddb5801e0.js → 6881.9e7955847640c7fe.js} +17 -17
- package/apps/pwa/out/_next/static/chunks/6931.fcdfb94141e67b3d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6991-8e081c6ce38c0ba1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7010.1bbd17a7f2b3e376.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7029.d1466acf55fae2ed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7152-a123aa8e129c131f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7455.196168fffac3b9c4.js +24 -0
- package/apps/pwa/out/_next/static/chunks/7638.6ca4770dd1129138.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7649-f37d76260e5e2a5a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7679.32c40cdd14f20b4a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7751.04bfbd16b4c6cd0a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7864.b1ff31d4030b8994.js +148 -0
- package/apps/pwa/out/_next/static/chunks/7869-c47bd2c075c1fefa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8138.6c10b2a356d52fca.js → 8442.2f09ca969d631615.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8445.2738dbd59a78c78c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8447.5510c439f9938c6d.js +286 -0
- package/apps/pwa/out/_next/static/chunks/{9415.69e4a21c60c7d2db.js → 8551.f762945c2cda2e95.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8635-57a9d8d9db70f4f1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/86961958-4b0689a5048f756b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8709.3c6019bb18f8554f.js +8 -0
- package/apps/pwa/out/_next/static/chunks/8759.c999528a0a907bf6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9a852a62-6192a6ea9346ced3.js → 87f498e5-0881de09f2e44707.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8843.c3197f904c952b81.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3d0154af.5331fec737ffe8c3.js → 892fd990.456232f326cc39c2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8965-135a2c9eaa2464cb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8971-8d3b9c96f8a6c958.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9059.c8598d96f8e5e9cd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9115.90447ae198b84eae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9147.d9b0a02e0babc9b4.js +93 -0
- package/apps/pwa/out/_next/static/chunks/9204-83075f4b70c61cdc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9244-c32ef14b5dc72bdf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/930.2a9a30f964350c66.js +90 -0
- package/apps/pwa/out/_next/static/chunks/9398.1f56e3f17795f43f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9527.3e5ad31c891c5fcb.js +79 -0
- package/apps/pwa/out/_next/static/chunks/9571-4ac933e774d1225c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-9fc644de2d8edae3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-0c9dace2db62bee8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f5e65c3dc9d89f2a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-2a9c19b9a140b7b1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-61533ad92141adfb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-112004ced6fd0329.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-f733318c237a49d8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-51ea677d2d33bbc0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-428b41f2f9566e3c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-1b29396ea90a61e5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-cf83ec613ac904bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-3f4c0ecbc284bb5f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-b63c322f54f70cc6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-fe328c4ee9aac6b4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-bfb855c584f0fb98.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-9fc644de2d8edae3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-47b1710c110eabd7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-42895214458bc6d2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-d03de0ebd8366e23.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-3ed6c47e4b1c61c2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-ae653e9bf261c738.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-23f3f0a0209c0ac4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-88c691273aae66f9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-e187ac52a95538b3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-6e45828daed1295e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-edb5d7287bb46186.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-4a7eaacd93c3f6a5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-3ca073d43d74cc84.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-1e9fee67b1199436.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-df1283ea963c6816.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-19fcf8d8301ccd18.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-5ab699e0303bbb40.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-7cde0d29756984c5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-9d4c598e8ee22f7a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-cc1609a71f44ec62.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-7659a9a1345bfaa9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-0afef60ecc0c4af5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-a3553949f8f34f5f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-9fc644de2d8edae3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-057a69702f3c67c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-c0f0e570f3f74c73.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-1825071ae34e3b89.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-6244e3e135d8ccfc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-da787e8a10b8a82e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-1316d32498e2be42.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-4df94572e343de2e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-708969f7475e88c1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-c58add08c919a1dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-a24847f45e5d5bf9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-acfec65a3a60e22e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-3dfc598e6fbde245.js +1 -0
- package/apps/pwa/out/_next/static/chunks/framework-c658a626118411b7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-4c73685fd891445b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-6310f7e5de7dd01d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-51c326bf48610be1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-d0beadf754da59b0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-acd243bfbf3f6732.js +1 -0
- package/apps/pwa/out/_next/static/css/d1f8bb401740a549.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 +23 -23
- 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 +22 -22
- 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 -1
- package/apps/pwa/out/live-run/index.txt +23 -23
- 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/catalog/external-commands.yaml +5 -3
- package/catalog/meta/catalogVersion.yaml +2 -1
- package/catalog/signing/catalog-signing-public.pem +3 -0
- package/graphs/intake/intake-daily.yaml +16 -0
- package/graphs/intake/news-sources.yaml +1 -0
- package/graphs/intake/recipes.yaml +7 -0
- package/graphs/overlays/README.md +6 -0
- package/graphs/overlays/examples/research-runtime-empty-terminal.yaml +28 -0
- package/graphs/profiles/local.yaml +5 -0
- package/graphs/profiles/pod.yaml +4 -0
- package/graphs/release/release-loop.yaml +33 -17
- package/graphs/units/verbs/make-doc.yaml +1 -0
- package/package.json +6 -2
- package/plugins/demo-absorption-briefing/plugin.json +18 -0
- package/plugins/demo-absorption-briefing/skills/demo-absorption-briefing/SKILL.md +8 -0
- package/plugins/demo-browser-hand/plugin.json +13 -0
- package/plugins/demo-browser-hand/skills/demo-browser-hand/SKILL.md +8 -0
- package/plugins/demo-linear-secretary/plugin.json +15 -0
- package/plugins/demo-linear-secretary/skills/demo-linear-secretary/SKILL.md +8 -0
- package/plugins/demo-plugin-wizard/plugin.json +11 -0
- package/plugins/demo-plugin-wizard/skills/demo-plugin-wizard/SKILL.md +8 -0
- package/plugins/demo-research/plugin.json +16 -0
- package/plugins/demo-research/skills/demo-research/SKILL.md +8 -0
- package/plugins/demo-video-briefing/plugin.json +15 -0
- package/plugins/demo-video-briefing/skills/demo-video-briefing/SKILL.md +8 -0
- package/scripts/bench-metering-plan.ts +229 -0
- package/scripts/build-signed-catalog.ts +48 -0
- package/scripts/ci-typecheck-main-delta.sh +88 -0
- package/scripts/codex-fallback-live-probe.ts +1 -1
- package/scripts/demo/demo1104-parallel-host.ts +56 -0
- package/scripts/demo/demo1104-parallel-scenes.ts +301 -0
- package/scripts/demo/demo1104-parallel.ts +72 -0
- package/scripts/demo/demo1104-stage.config.ts +21 -0
- package/scripts/demo/demo1104-stage.ts +316 -0
- package/scripts/goal-method-audit.ts +17 -1
- package/scripts/graph-units/land-enqueue.ts +90 -0
- package/scripts/hooks/codex-notify.sh +32 -0
- package/scripts/install.sh +10 -38
- package/scripts/landing-heal/land-plan.ts +57 -0
- package/scripts/public-leak-baseline.txt +0 -1
- package/scripts/release-loop/auto-approve-node.ts +1 -1
- package/scripts/release-loop/docs-node.ts +4 -2
- package/scripts/release-loop/gate-autoheal-sim-evidence.ts +60 -0
- package/scripts/release-loop/gate-image-pin.ts +116 -2
- package/scripts/release-loop/gate-method.ts +139 -0
- package/scripts/release-loop/gate-node.ts +395 -43
- package/scripts/release-loop/gate-progress-sim.ts +73 -0
- package/scripts/release-loop/gate-traffic-mode.ts +151 -3
- package/scripts/release-loop/node-verdict.ts +38 -25
- package/scripts/release-loop/notes-repair.ts +18 -1
- package/scripts/release-loop/preflight.ts +544 -10
- package/scripts/release-loop/render-warm-control-manifest.ts +22 -0
- package/scripts/release-loop/resume-release.ts +9 -7
- package/scripts/release-loop/resume.ts +13 -4
- package/scripts/release-loop/unattended-release.ts +20 -9
- package/scripts/release-loop/warm-control-forward.ts +178 -0
- package/scripts/retro-cycle.ts +30 -8
- package/scripts/rhythm/daily.ts +39 -4
- package/scripts/rhythm/linear-progress-sync.ts +311 -0
- package/scripts/ux-sim.ts +7 -1
- package/scripts/video-pipeline.ts +9 -1
- package/scripts/warm-pool-provider-config.ts +35 -7
- package/src/acp/agent-manager.ts +1 -0
- package/src/acp/codex-app-server-agent.ts +33 -0
- package/src/acp/codex-app-server-client.ts +153 -16
- package/src/acp/core-turn-bridge.ts +17 -12
- package/src/acp/event-router.ts +20 -1
- package/src/acp/server.ts +6 -0
- package/src/agent-mission/browser-roundtrip-coordinator.ts +85 -0
- package/src/agent-mission/claude-headless.ts +4 -1
- package/src/agent-mission/codex-exec.ts +105 -0
- package/src/agent-mission/driver.ts +192 -11
- package/src/agent-mission/grok-auth-status.ts +107 -0
- package/src/agent-mission/hand-hooks.ts +101 -0
- package/src/agent-mission/mission-cli.ts +56 -21
- package/src/agent-mission/mission-hand-reply.ts +118 -0
- package/src/agent-mission/mission-line.ts +180 -0
- package/src/agent-mission/mission-out.ts +256 -18
- package/src/agent-mission/tool-install.ts +122 -11
- package/src/agent-substrate/pr-reviewer.ts +147 -11
- package/src/agent-substrate/self-review-cli.ts +10 -2
- package/src/agents/general-purpose.md +1 -1
- package/src/auto-research/research-fanout.ts +221 -0
- package/src/auto-research/research-turn.ts +362 -31
- package/src/autopilot/mission-codebase-gate.ts +7 -3
- package/src/autopilot/pr-manager.ts +31 -3
- package/src/boot/daemon-tools/self-implement.ts +3 -1
- package/src/boot/daemon-tools/web-search.ts +1 -2
- package/src/briefing/brief-items.ts +55 -11
- package/src/cli/agent-cli.ts +2 -1
- package/src/cli/catalog-cli.ts +31 -0
- package/src/cli/connector-cli.ts +16 -0
- package/src/cli/control-cli.ts +15 -6
- package/src/cli/doctor-fix.ts +2 -2
- package/src/cli/intake-source-cli.ts +44 -8
- package/src/cli/log-event-names.ts +8 -0
- package/src/cli/msg-cli.ts +32 -3
- package/src/cli/pr-cli.ts +21 -1
- package/src/cli/pr-land-heavy-remote.ts +44 -6
- package/src/cli/pr-land-overlap-evidence.ts +13 -1
- package/src/cli/pr-land-pod-shadow-job.ts +143 -5
- package/src/cli/pr-land-pod-shadow.ts +9 -2
- package/src/cli/release-checklist-read-cli.ts +44 -16
- package/src/cli/release-cli.ts +23 -3
- package/src/cli/research-cli.ts +9 -0
- package/src/cli/setup-cli.ts +166 -6
- package/src/cli/setup-keys.ts +199 -0
- package/src/cli/task-plugin-wizard-host.ts +114 -0
- package/src/cli/tasks-cli.ts +54 -9
- package/src/cli/tui-test-cli.ts +190 -0
- package/src/config.ts +4 -2
- package/src/connectors/linear-daily-progress.ts +228 -0
- package/src/connectors/linear-projector.ts +34 -12
- package/src/connectors/linear.ts +151 -7
- package/src/control-plane/ledger.ts +42 -4
- package/src/control-plane/member-tokens.ts +25 -2
- package/src/control-plane/ports.ts +8 -3
- package/src/control-plane/server.ts +26 -5
- package/src/core-turn/deep-turn-bloom.ts +67 -0
- package/src/core-turn/research-policy.ts +43 -0
- package/src/core-turn/run-core-turn.ts +488 -51
- package/src/core-turn/types.ts +4 -0
- package/src/dashboard/chat/acp-chat.ts +10 -0
- package/src/dashboard/classic-sidetab-mission-job.ts +192 -0
- package/src/dashboard/classic-sidetab-mission-pair.ts +82 -0
- package/src/dashboard/classic-sidetab-mission-trio.ts +242 -0
- package/src/dashboard/classic-sidetab-mission.ts +169 -5
- package/src/dashboard/index.ts +387 -14
- package/src/dashboard/input/chat-main-acp-dispatch.ts +23 -0
- package/src/dashboard/input/chat-main-plain-turn-runtime.ts +8 -0
- package/src/dashboard/mission-cli-consumer.ts +20 -8
- package/src/dashboard/plugin-wizard-runtime.ts +50 -0
- package/src/dashboard/plugin-wizard-tool.ts +55 -0
- package/src/dashboard/sidetab-runtime.ts +3 -1
- package/src/dashboard/terminal-popup-runtime.ts +6 -3
- package/src/dashboard/visible-mission-diagnostics.ts +16 -0
- package/src/decisions/linear-progress-summary.ts +78 -0
- package/src/decisions/linear-progress-sync.ts +152 -0
- package/src/domains/outbound-alert.ts +49 -11
- package/src/domains/retro-cycle-observation.ts +43 -0
- package/src/domains/retro-cycle.ts +13 -2
- package/src/domains/retro-proposals.ts +92 -0
- package/src/domains/retro-report.ts +6 -1
- package/src/domains/telegram-kind-route.ts +16 -9
- package/src/graph-runner/arg-allowance.ts +13 -0
- package/src/graph-runner/graph-cli.ts +24 -1
- package/src/graph-runner/graph-grow-llm.ts +3 -1
- package/src/graph-runner/graph-run-api.ts +3 -1
- package/src/graph-runner/runner.ts +310 -46
- package/src/graph-wizard/mission-compose.ts +407 -12
- package/src/harness/dispatch-backpressure.ts +54 -1
- package/src/harness/dispatch-shadow-compare.ts +74 -0
- package/src/harness/harness-cli-command.ts +15 -3
- package/src/harness/harness-pod-dispatch.ts +3 -0
- package/src/harness/skill-exec.ts +21 -9
- package/src/harness/skill-select.ts +1 -1
- package/src/hooks/error-report-cloud.ts +101 -0
- package/src/hooks/receiver.ts +29 -2
- package/src/index.ts +129 -12
- package/src/intake-plane/actions.ts +354 -0
- package/src/intake-plane/adapters/telegram-ambient.ts +34 -18
- package/src/intake-plane/intake-sources.ts +78 -8
- package/src/intake-plane/items.ts +14 -4
- package/src/intake-plane/linear-mission-sync.ts +80 -0
- package/src/intake-plane/linear-secretary-runtime.ts +178 -0
- package/src/intake-plane/linear-wish.ts +227 -1
- package/src/life/life-cli.ts +168 -0
- package/src/life/life-kinds.ts +160 -0
- package/src/live-supply/budget-consumer.ts +78 -0
- package/src/live-supply/read.ts +3 -0
- package/src/live-supply/snapshot.ts +45 -0
- package/src/llm/child-first-llm-observation.ts +112 -0
- package/src/llm/provider-credentials.ts +68 -6
- package/src/llm/provider-failure-evidence.ts +44 -0
- package/src/llm.ts +172 -12
- package/src/maturity/feature-maturity.ts +3 -3
- package/src/mcp/client.ts +7 -0
- package/src/mcp/proxy-runtime.ts +6 -1
- package/src/mcp/server.ts +54 -0
- package/src/msg/msg-sse-server.ts +3 -3
- package/src/msg/msg-store.ts +177 -19
- package/src/native-tool-catalog.ts +36 -2
- package/src/nexus/api/consult-requests.ts +18 -6
- package/src/nexus/api/discord-trigger-bot.ts +6 -6
- package/src/nexus/api/graphs-api.ts +4 -2
- package/src/nexus/api/grokbot-trigger-bot.ts +206 -0
- package/src/nexus/api/http-server.ts +37 -1
- package/src/nexus/api/mcp-public-allowlist.ts +2 -0
- package/src/nexus/api/telegram-trigger-bot.ts +6 -6
- package/src/nexus/boot/register-mcp-clients.ts +114 -8
- package/src/nexus/index.ts +22 -2
- package/src/nexus/outbound/format.ts +9 -0
- package/src/nexus/outbound/grokbot-adapter.ts +71 -0
- package/src/nexus/outbound/router.ts +121 -16
- package/src/nexus/setup-status.ts +9 -0
- package/src/nexus/state/state.ts +3 -0
- package/src/oauth/codex-account-store.ts +20 -7
- package/src/oauth/codex-owner-mirror.ts +81 -0
- package/src/oauth/codex.ts +17 -2
- package/src/oauth/fallback-chain.ts +47 -17
- package/src/onboarding/first-run-doctor.ts +8 -1
- package/src/onboarding/first-run.ts +126 -29
- package/src/onboarding/key-setup.ts +103 -0
- package/src/onboarding/menu-answer.ts +13 -0
- package/src/onboarding/secret-prompt.ts +37 -0
- package/src/plugins/install/plugin-install.ts +59 -0
- package/src/pr-tool/census.ts +130 -0
- package/src/preview/terminal.ts +22 -7
- package/src/prompt-enhance/enhance.ts +10 -0
- package/src/pty-shell/pty-control-ipc.ts +4 -1
- package/src/pty-shell/pty-write-provenance.ts +21 -3
- package/src/pty-shell/registry.ts +9 -2
- package/src/registry/catalog-server.ts +232 -0
- package/src/registry/loader.ts +45 -1
- package/src/registry/types.ts +1 -2
- package/src/release-loop/proposal-auto-place.ts +83 -0
- package/src/research/axis-fanout.ts +79 -29
- package/src/research/lenses/second-order.ts +312 -0
- package/src/self-dev/ask-launch-flow.ts +72 -1
- package/src/self-dev/dev-cli.ts +7 -2
- package/src/self-dev/dev-pipeline.ts +5 -0
- package/src/self-dev/draft-residue.ts +64 -5
- package/src/self-dev/draft-sweep.ts +31 -10
- package/src/self-dev/draft-triage-rules.ts +60 -3
- package/src/self-dev/graph-front-nodes.ts +2 -1
- package/src/self-dev/graph-journey-route.ts +76 -21
- package/src/self-dev/launch-preflight.ts +58 -0
- package/src/self-dev/orchestrate.ts +1 -1
- package/src/self-dev/run-supervisor.ts +152 -2
- package/src/self-dev/shard-aggregate.ts +4 -0
- package/src/self-dev/shard-run.ts +36 -15
- package/src/self-implement/abandoned-classification.ts +12 -3
- package/src/self-implement/budget-gate.ts +7 -0
- package/src/self-implement/child-llm-preference.ts +10 -1
- package/src/self-implement/doctor-visit.ts +30 -8
- package/src/self-implement/elanous-tui-spawn.ts +3 -0
- package/src/self-implement/fixtures/tdd-green-missing-passing-child-transcript.txt +2 -0
- package/src/self-implement/gate-artifacts.ts +2 -1
- package/src/self-implement/goal-author-cli.ts +4 -2
- package/src/self-implement/goal-author.ts +549 -10
- package/src/self-implement/graph-overlay-yaml.ts +181 -4
- package/src/self-implement/graph-overlay.ts +3 -1
- package/src/self-implement/graph-profile-yaml.ts +107 -0
- package/src/self-implement/graph-yaml.ts +94 -5
- package/src/self-implement/off-diff-evidence.ts +249 -5
- package/src/self-implement/orchestrator.ts +298 -26
- package/src/self-implement/run-chart.ts +6 -3
- package/src/self-implement/run-ledger.ts +51 -1
- package/src/self-implement/runtime-overlay-run-steps.ts +95 -0
- package/src/self-implement/tdd-evidence-tags.ts +9 -0
- package/src/self-implement/test-scenario-request.ts +37 -6
- package/src/self-implement/train-gate-receipt.ts +166 -0
- package/src/self-implement/train-gate-remote.ts +62 -18
- package/src/session/chat.ts +6 -4
- package/src/session/session-fanout.ts +2 -0
- package/src/session/streaming/acp-stream-sink.ts +2 -1
- package/src/session/streaming/chunk-producer.ts +6 -0
- package/src/session/streaming/stream-compositor.ts +3 -2
- package/src/session-runtime/index.ts +74 -6
- package/src/session-runtime/retry-policy.ts +1 -1
- package/src/setup/llm-env-detect.ts +3 -0
- package/src/setup/service-key-catalog.ts +46 -0
- package/src/setup/service-keys.ts +241 -0
- package/src/sidetab-core/model.ts +205 -10
- package/src/sidetab-core/visible-agent-mission-pair.ts +51 -0
- package/src/sidetab-core/visible-agent-mission.ts +182 -48
- package/src/skills/tools/agent.ts +23 -4
- package/src/skills/tools/omni-search.ts +127 -27
- package/src/task-agent/parent-host.ts +29 -1
- package/src/task-agent/recall-inject.ts +98 -0
- package/src/task-agent/repair-outcomes.ts +65 -0
- package/src/task-agent/task-hand.ts +46 -14
- package/src/task-orchestrator/dispatcher.ts +14 -1
- package/src/task-orchestrator/linear-capture-events.ts +55 -0
- package/src/task-orchestrator/store.ts +16 -5
- package/src/task-orchestrator/surfaces/pod-artifact-return.ts +23 -1
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +107 -1
- package/src/task-orchestrator/surfaces/pod-ledger-events.ts +113 -14
- package/src/task-orchestrator/surfaces/pod-si-warm-host.ts +21 -9
- package/src/task-orchestrator/surfaces/pod-source-receive.ts +23 -11
- package/src/task-orchestrator/surfaces/pod-warm-switch.ts +2 -0
- package/src/task-orchestrator/surfaces/pod-warm-worker-pod.ts +1 -1
- package/src/task-orchestrator/surfaces/pod-warm-worker-provider.ts +467 -77
- package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +45 -3
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +210 -14
- package/src/task-orchestrator/surfaces/self-implement.ts +27 -3
- package/src/task-orchestrator/surfaces/warm-reuse-evidence.ts +169 -0
- package/src/task-orchestrator/types.ts +14 -0
- package/src/telegram.ts +14 -16
- package/src/terminal/coding-agent.ts +14 -5
- package/src/terminal/session-registry.ts +1 -1
- package/src/tool-hints/gate.ts +46 -14
- package/src/tool-hints/signals.ts +1 -1
- package/src/tool-hints/types.ts +2 -0
- package/src/tool-runtime/agent-runtime.ts +1 -0
- package/src/tool-runtime/browser-hand-runtime.ts +391 -0
- package/src/tool-runtime/browser-runtime.ts +33 -19
- package/src/tool-runtime/elanous-linear-issue-runtime.ts +76 -0
- package/src/tool-runtime/elanous-linear-secretary-runtime.ts +173 -0
- package/src/tool-runtime/graph-mcp-runtimes.ts +240 -0
- package/src/tool-runtime/index.ts +6 -0
- package/src/tool-runtime/types.ts +5 -0
- package/src/tui/entry.ts +51 -8
- package/src/tui/sidetab/attach.ts +252 -11
- package/src/tui/sidetab/herdr-portal.ts +225 -0
- package/src/tui/sidetab/list.ts +3 -2
- package/src/tui-client/next-dashboard-runtime.ts +52 -0
- package/src/user-config.ts +34 -9
- package/src/user-intent/types.ts +4 -3
- package/src/ux-sim/browser-hand.ts +165 -0
- package/src/ux-sim/draft-inventory-builder.ts +199 -0
- package/src/ux-sim/fake-browser-page.ts +81 -0
- package/src/ux-sim/fake-linear-device.ts +31 -0
- package/src/ux-sim/graph-engine-sim.ts +47 -0
- package/src/ux-sim/linear-secretary.ts +208 -0
- package/src/ux-sim/mock-scenario-catalog.json +81 -26
- package/src/ux-sim/mock-scenario-catalog.ts +175 -5
- package/src/ux-sim/onboarding-first-run-device.ts +31 -21
- package/src/ux-sim/release-sim-rig.ts +62 -18
- package/src/ux-sim/scenario.ts +70 -26
- package/src/ux-sim/service-key-setup.ts +113 -0
- package/src/ux-sim/term-slash-device.ts +74 -0
- package/src/ux-sim/turn-throw-sim.ts +31 -0
- package/src/version/packed-revision.json +1 -1
- package/src/web-search/exa.ts +77 -0
- package/src/web-search/index.ts +30 -9
- package/src/web-search/provider.ts +4 -1
- package/src/web-search/x-intent.ts +5 -0
- package/apps/pwa/out/_next/static/chunks/0244e5b5.6fc7670f8223d82d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1121.13ba7a8aff19df81.js +0 -36
- package/apps/pwa/out/_next/static/chunks/1147-5196d19c222051b3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1319.b9b815ae0f181e06.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1345-31427b833438f521.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1455-530ca16c7e71baff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1574.878c18ecc97b1bd4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1586.fafc33a9a5835cb7.js +0 -32
- package/apps/pwa/out/_next/static/chunks/1744.855fd60ae4c4a8c8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/193.21a47a3e07ef6860.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1945.7b54990d578c119b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2004.e839fda7ea5a738a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2044-dd681a5c08779ff8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2101.2248decb38829b03.js +0 -286
- package/apps/pwa/out/_next/static/chunks/2300.41ea70ac1bef1446.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2357-07b5eeb5fb12628d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2364.28652020695a6db3.js +0 -34
- package/apps/pwa/out/_next/static/chunks/2456.b6de0e1c05f28843.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2526.c3e50ac1fec064bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2549-cda50b716ff4250c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2550-5f1e88f615568045.js +0 -1
- package/apps/pwa/out/_next/static/chunks/261-319078c954e40d66.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2690.0521c738b7749931.js +0 -115
- package/apps/pwa/out/_next/static/chunks/2974.595d1e29d040cb6a.js +0 -64
- package/apps/pwa/out/_next/static/chunks/3207.a756c95e9ec8c3c8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3285.89ee2f298682f5e5.js +0 -93
- package/apps/pwa/out/_next/static/chunks/3447.06edc7b092021417.js +0 -41
- package/apps/pwa/out/_next/static/chunks/3510-9ca392698bfe4391.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3541.772e5ce425aaa611.js +0 -24
- package/apps/pwa/out/_next/static/chunks/3741.9cf76acaa2ee5298.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3837.a210d7d0db8d8abd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3898-955c896f394217a6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3947-ef57acea76eaa8d2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4037-3b82ebc7d78afdc7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/405.9022d4a478202b67.js +0 -77
- package/apps/pwa/out/_next/static/chunks/417-fb2cc9a9910e5968.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4276.336d76d858fcdf1b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4302.6ced9aa4535de947.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4320.583cbab64db6be02.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4768-8eac183fd291ca53.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4770.769c9cd9c7034eba.js +0 -148
- package/apps/pwa/out/_next/static/chunks/4895.65605ec3e6504e89.js +0 -74
- package/apps/pwa/out/_next/static/chunks/50061469.0326382241ef3b2d.js +0 -122
- package/apps/pwa/out/_next/static/chunks/5112.4b9f300124fab7b3.js +0 -149
- package/apps/pwa/out/_next/static/chunks/5335.375ee922bcd5bc83.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5342.023a2e729689b320.js +0 -27
- package/apps/pwa/out/_next/static/chunks/53c73f84.373b6160adfa89a4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5431-72a1dbde59400691.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5440-4236de26c5ea5856.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5486-b318c165f645d8ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5506-c7d2ec25c62a7c94.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5570-c66ecbea26b4e3b1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5696.ffca5aa259d8451e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5793-8c584aea2aa87560.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5848-6de6b8c173351a90.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5999.94e3c83040200f31.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5a7901d6-2d8d9b5587cf9042.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6131.348b7d6bdcd267ce.js +0 -226
- package/apps/pwa/out/_next/static/chunks/6164.86133a0172611184.js +0 -90
- package/apps/pwa/out/_next/static/chunks/6515.e07e8b2b8a707a87.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6522-c005514f87d18982.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6642.648adb1767f39ecf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6848.138805a09d001d09.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7049-c09e2b7a253a24f3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7072.1e00447d9fa39972.js +0 -104
- package/apps/pwa/out/_next/static/chunks/7106.64201c539c5e220c.js +0 -94
- package/apps/pwa/out/_next/static/chunks/7151-1f742b455986530e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7199.18d6b25e001154ce.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7279-c199ea83f34338fa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/728b938d.ffaa7ad583d60e58.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7309.171c3de54e0322f1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/733.7a1ab3342e03306f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7504.d5cf1e7cd89b1b08.js +0 -24
- package/apps/pwa/out/_next/static/chunks/7573-0fd4af62d65d4598.js +0 -1
- package/apps/pwa/out/_next/static/chunks/759.6f8267df4689c401.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7825-668b046f72f14483.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7852-1538768b6eb19388.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7868.cff810430bb63c91.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8084-dfcc796874ab4e07.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8266.6bf9b131b2584678.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8403.7b81d5e64074a725.js +0 -8
- package/apps/pwa/out/_next/static/chunks/8470.1998cbd0866a7eac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8505.fd803c6c33b54cc3.js +0 -79
- package/apps/pwa/out/_next/static/chunks/8546-6f03f8ea34dd61d9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8687-246fda8433d3477c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/898-280022fb082936bc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9098-50eefc1844212e3d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9211-330b6736a63d4cb3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9249.976f1e69a9d80754.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9255-cf88fceda5666645.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9306-e523ba58bb8adc42.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9324.a9866d717ba62cb6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9325.a8510e8aa87c8143.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9353-12bfff0c130104cf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9479-154a697d4b6e4e98.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9540-65aa6ccc31b3fd78.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9633.ce5bddc56784da72.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9656-374232558fd528d0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9832.f4a98c878ecf2c0a.js +0 -62
- package/apps/pwa/out/_next/static/chunks/9865.f094440645f02d98.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-e772b4710df5d936.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-4361368e6a76b967.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-802f349fdd65248d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-7f35513988a2a495.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-798121656540e33e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-20a089490affe98c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-e5e4ec786d70e604.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-d12fe5d0801430e4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-d8cf21022fecc026.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-21ce565c9c57ee97.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-b56563025f619c9b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-52f14dd133da8348.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-c0ed7fdcdba76633.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-af8c3020cb520f15.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-72eda67af9f5e35c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-e772b4710df5d936.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-67a6620d31f0a46f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-f059363aa0880ec2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-f9f917d6a57f69fb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-d3ae55eee2f0826c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-0e3d7b9421b79dac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-ba738f7e233eb211.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-33438b98c74fd599.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-0b34d5fd990d793a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-0f291d3c8fd55a3a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-3fced192be5279c1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-04cb3cf182a7c1ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-6ccbd2ab23a21542.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-aa0518c48a5f4e56.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-4b32226d341ad7d7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-3224fd8f6383b61f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-efbc97459f2914e2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-2fe487d88cd5d97c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-48861dd1a858bab7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-197d5d4d21d228ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a763ba89fbdd9c27.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-7edb253510b1c136.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-0c875702c61965ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-e772b4710df5d936.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-e425c951151095f7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-9c90e0aec4e27f77.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-733e09a02e727d42.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-4f899e14c34d77a1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-bf067887b3307423.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-7af10868ff87d04e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-efe5304057aa11c7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-76bcd29aaa8a26c3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-4cfd88e2273ce50f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-0a1ddb47efba0a32.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2a176c707152c892.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-efe03031b8797fbf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-d0d2d673bfd8a8a9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-4c9fceb2baa09cb7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-b0ab8d8fc9a97556.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-7e2cc62a2e5cec96.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-22324935f753ec5e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-9dc1bfa25334998a.js +0 -1
- package/apps/pwa/out/_next/static/css/be9019f696217521.css +0 -1
- /package/apps/pwa/out/_next/static/{X1ucLw10Cds_h614NoEnh → HMXsxdy5MIIcHPMHv1wD4}/_ssgManifest.js +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Fenced warm-Pod lease bridge. The Job remains authoritative. */
|
|
2
2
|
import { spawn, spawnSync } from 'node:child_process';
|
|
3
|
-
import { isAbsolute, join } from 'node:path';
|
|
4
|
-
import { constants, closeSync, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, isAbsolute, join } from 'node:path';
|
|
4
|
+
import { constants, closeSync, existsSync, fsyncSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
5
5
|
import { tmpdir, userInfo } from 'node:os';
|
|
6
6
|
import { dlopen, read, type Pointer } from 'bun:ffi';
|
|
7
7
|
import { createHash, randomUUID } from 'node:crypto';
|
|
@@ -19,6 +19,8 @@ const POD_READY_WAIT_SECONDS = 900;
|
|
|
19
19
|
const POD_READY_RECHECK_SECONDS = 300;
|
|
20
20
|
const POD_READY_RETRY_DELAY_MS = 1_000;
|
|
21
21
|
const WARM_CONTROL_REQUEST_TIMEOUT_MS = 2_000;
|
|
22
|
+
const WARM_POD_LIST_PAGE_SIZE = 25;
|
|
23
|
+
const WARM_POD_LIST_MAX_PAGES = 20;
|
|
22
24
|
|
|
23
25
|
function throwIfPrepareAborted(signal?: AbortSignal): void {
|
|
24
26
|
if (!signal?.aborted) return;
|
|
@@ -45,12 +47,17 @@ function waitForPrepareRetry(signal?: AbortSignal): Promise<void> {
|
|
|
45
47
|
export type WarmReplayInput = Omit<WarmWorkerCommand, 'leaseId' | 'generation' | 'workerId' | 'key'>;
|
|
46
48
|
export interface WarmReplayResult { command: WarmWorkerCommand | null; transport: WarmPodTransportResult }
|
|
47
49
|
export interface WarmWorkerCandidate { host: string; tier: WarmPoolTier; spec: WarmPodSpec }
|
|
50
|
+
export interface WarmPodDeleteOptions {
|
|
51
|
+
apiVersion: 'v1'; kind: 'DeleteOptions'; preconditions: { uid: string };
|
|
52
|
+
}
|
|
48
53
|
/** The OS `flock(fd, operation)` binding the prepare lease waits on. */
|
|
49
54
|
export type WarmPrepareFlock = (fd: number, operation: number) => number;
|
|
50
55
|
export interface WarmPodProviderOptions {
|
|
51
56
|
candidates: readonly WarmWorkerCandidate[];
|
|
52
|
-
/** One pinned template per host/tier.
|
|
57
|
+
/** One pinned template per host/tier. It creates one candidate unless expansion is opted in. */
|
|
53
58
|
poolTemplates?: readonly WarmWorkerCandidate[];
|
|
59
|
+
/** Expand each pool template to its measured profile quota. Defaults to false. */
|
|
60
|
+
expandPoolTemplatesToQuota?: boolean;
|
|
54
61
|
/** This address is resolved by the control-plane join/primary mechanism, never guessed from a Pod context. */
|
|
55
62
|
controlUrl: string;
|
|
56
63
|
memberToken: string;
|
|
@@ -62,9 +69,19 @@ export interface WarmPodProviderOptions {
|
|
|
62
69
|
transport?: (spec: WarmPodSpec) => Pick<WarmPodTransport, 'request'>;
|
|
63
70
|
maxShadowMs?: number;
|
|
64
71
|
now?: () => number;
|
|
65
|
-
kubectl?: (args: readonly string[], input?: string) => {
|
|
72
|
+
kubectl?: (args: readonly string[], input?: string) => {
|
|
73
|
+
status: number | null; stdout: string; stderr: string; error?: NodeJS.ErrnoException;
|
|
74
|
+
};
|
|
66
75
|
/** Private directory for cross-process prepare leases; defaults to a per-user directory under tmpdir(). */
|
|
67
76
|
prepareLockDirectory?: string;
|
|
77
|
+
/** Private durable slot-generation file. Set by loadWarmPodProvider for production reconciliation. */
|
|
78
|
+
rotationStatePath?: string;
|
|
79
|
+
/** Test-only loopback API endpoint for exercising the production DeleteOptions request. */
|
|
80
|
+
kubectlProxyUrlForTest?: string;
|
|
81
|
+
/** Test-only quota resolver injection; omitted keeps the production measured-host table. */
|
|
82
|
+
quotaResolverForTest?: typeof resolveWarmPoolQuota;
|
|
83
|
+
/** Separate fetch for the Kubernetes API; control-plane calls continue using fetch. */
|
|
84
|
+
kubernetesFetch?: typeof fetch;
|
|
68
85
|
/** Cancels a prepare waiting for another process to finish the same host/worker slot. */
|
|
69
86
|
prepareAbortSignal?: AbortSignal;
|
|
70
87
|
/** Injects the prepare-lease flock binding. Omitted keeps this platform's binding; null/false declares it unsupported. */
|
|
@@ -72,7 +89,7 @@ export interface WarmPodProviderOptions {
|
|
|
72
89
|
/** Gate file this pool serves, used to bind cold prepare legs to their 1-file gate measurement key. */
|
|
73
90
|
pairingGateFile?: string;
|
|
74
91
|
}
|
|
75
|
-
type WarmPodProviderRuntimeOverrides = Pick<WarmPodProviderOptions, 'fetch' | 'kubectl' | 'prepareLockDirectory'>;
|
|
92
|
+
type WarmPodProviderRuntimeOverrides = Pick<WarmPodProviderOptions, 'controlUrl' | 'fetch' | 'kubectl' | 'prepareLockDirectory'>;
|
|
76
93
|
export interface WarmPodProviderLoadOptions {
|
|
77
94
|
pairingGateFile?: string;
|
|
78
95
|
runtimeOverrides?: Partial<WarmPodProviderRuntimeOverrides>;
|
|
@@ -84,6 +101,19 @@ export interface WarmPoolReconcileResult {
|
|
|
84
101
|
ready: number;
|
|
85
102
|
failed: readonly string[];
|
|
86
103
|
}
|
|
104
|
+
type WarmPoolFailureDecision = { stage: string; reason: string; stderr?: string };
|
|
105
|
+
|
|
106
|
+
const WARM_POOL_STDERR_LIMIT = 512;
|
|
107
|
+
function safeWarmPoolStderr(stderr: string, secrets: readonly string[]): string {
|
|
108
|
+
let safe = stderr;
|
|
109
|
+
for (const secret of secrets.filter(value => value.length >= 8)) safe = safe.split(secret).join('[REDACTED]');
|
|
110
|
+
safe = safe.replace(/(authorization\s*[:=]\s*bearer\s+)[^\s,;]+/gi, '$1[REDACTED]')
|
|
111
|
+
.replace(/(bearer\s+)[A-Za-z0-9._~+/=-]+/gi, '$1[REDACTED]')
|
|
112
|
+
.replace(/((?:token|password|secret|api[_-]?key|credential)\s*[:=]\s*)(?:"[^"]*"|'[^']*'|[^\s,;]+)/gi,
|
|
113
|
+
'$1[REDACTED]');
|
|
114
|
+
return safe.length > WARM_POOL_STDERR_LIMIT
|
|
115
|
+
? `${safe.slice(0, WARM_POOL_STDERR_LIMIT)}…[truncated]` : safe;
|
|
116
|
+
}
|
|
87
117
|
|
|
88
118
|
/** One leg of a read-only warm/cold prepare-time measurement pair, bound to one 1-file gate key. */
|
|
89
119
|
export interface WarmPreparePairLeg {
|
|
@@ -227,7 +257,7 @@ async function withPrepareLease<T>(candidate: WarmWorkerCandidate, directory: st
|
|
|
227
257
|
export class WarmPodProvider {
|
|
228
258
|
private readonly fetcher: (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
229
259
|
private readonly now: () => number;
|
|
230
|
-
private readonly candidates:
|
|
260
|
+
private readonly candidates: WarmWorkerCandidate[];
|
|
231
261
|
private readonly profile: WarmPoolProfile;
|
|
232
262
|
/** This process's own legs, echoed on CLI stdout. Pairing never reads this — see readWarmReuseTimePairs. */
|
|
233
263
|
private readonly legs: WarmReuseTimeLegRow[] = [];
|
|
@@ -241,20 +271,24 @@ export class WarmPodProvider {
|
|
|
241
271
|
private readonly standingWorkers = new Map<string, Promise<boolean>>();
|
|
242
272
|
/** This instance's prepare-lease flock binding; null means the platform cannot lock and every slot fails. */
|
|
243
273
|
private readonly prepareFlock: WarmPrepareFlock | null;
|
|
274
|
+
private readonly quotaResolver: typeof resolveWarmPoolQuota;
|
|
244
275
|
constructor(private readonly options: WarmPodProviderOptions) {
|
|
245
276
|
const url = new URL(options.controlUrl);
|
|
246
277
|
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.search || url.hash
|
|
247
278
|
|| url.pathname !== '/' || !token(options.memberToken)) throw new Error('invalid warm control address');
|
|
279
|
+
this.quotaResolver = options.quotaResolverForTest ?? resolveWarmPoolQuota;
|
|
248
280
|
const profile = resolveWarmPoolProfile(options.profileConfig ?? getUserConfig());
|
|
249
281
|
this.profile = profile;
|
|
250
282
|
const templates = options.poolTemplates ?? [];
|
|
251
283
|
if (new Set(templates.map(candidate => `${candidate.host}/${candidate.tier}`)).size !== templates.length)
|
|
252
284
|
throw new Error('duplicate warm pool template');
|
|
253
285
|
const expanded = templates.flatMap(candidate => {
|
|
254
|
-
const quota =
|
|
286
|
+
const quota = this.quotaResolver(profile, candidate.host, candidate.tier);
|
|
255
287
|
if (!quota || !candidate.spec.nodeName) throw new Error('unmeasured warm pool template');
|
|
256
|
-
|
|
257
|
-
|
|
288
|
+
const slots = options.expandPoolTemplatesToQuota === true ? quota : 1;
|
|
289
|
+
return Array.from({ length: slots }, (_, index): WarmWorkerCandidate => ({
|
|
290
|
+
host: candidate.host, tier: candidate.tier,
|
|
291
|
+
spec: {
|
|
258
292
|
...candidate.spec,
|
|
259
293
|
name: `warm-${candidate.host}-${candidate.tier}-${index + 1}`,
|
|
260
294
|
// The template worker ID is a generation token. Rotate it when a slot is quarantined.
|
|
@@ -262,9 +296,16 @@ export class WarmPodProvider {
|
|
|
262
296
|
},
|
|
263
297
|
}));
|
|
264
298
|
});
|
|
265
|
-
this.candidates = [...options.candidates, ...expanded];
|
|
299
|
+
this.candidates = [...options.candidates.map(row => ({ ...row, spec: { ...row.spec } })), ...expanded];
|
|
300
|
+
if (options.rotationStatePath) {
|
|
301
|
+
const rotations = this.readRotations(options.rotationStatePath);
|
|
302
|
+
for (const row of this.candidates) {
|
|
303
|
+
const rotated = rotations[`${row.host}/${row.spec.name}`];
|
|
304
|
+
if (rotated) row.spec = { ...row.spec, workerId: rotated };
|
|
305
|
+
}
|
|
306
|
+
}
|
|
266
307
|
if (this.candidates.some(candidate => !workerId(candidate.spec.workerId)
|
|
267
|
-
|| !candidate.host.trim() ||
|
|
308
|
+
|| !candidate.host.trim() || this.quotaResolver(profile, candidate.host, candidate.tier) < 1
|
|
268
309
|
|| candidate.spec.memoryLimit !== (candidate.tier === 'lite' ? '2Gi' : '16Gi')
|
|
269
310
|
|| !warmWorkerPodManifest(candidate.spec))) throw new Error('invalid warm candidate');
|
|
270
311
|
if (new Set(this.candidates.map(candidate => candidate.spec.workerId)).size !== this.candidates.length)
|
|
@@ -277,7 +318,7 @@ export class WarmPodProvider {
|
|
|
277
318
|
names.add(identity);
|
|
278
319
|
const slot = `${candidate.host}/${candidate.tier}`;
|
|
279
320
|
const count = (counts.get(slot) ?? 0) + 1;
|
|
280
|
-
if (count >
|
|
321
|
+
if (count > this.quotaResolver(profile, candidate.host, candidate.tier))
|
|
281
322
|
throw new Error('warm candidate quota exceeded');
|
|
282
323
|
counts.set(slot, count);
|
|
283
324
|
}
|
|
@@ -299,7 +340,8 @@ export class WarmPodProvider {
|
|
|
299
340
|
return { status: response.status, value };
|
|
300
341
|
}
|
|
301
342
|
|
|
302
|
-
private async controlBounded(path: string, credential: string, body: Record<string, unknown
|
|
343
|
+
private async controlBounded(path: string, credential: string, body: Record<string, unknown> | undefined,
|
|
344
|
+
timeoutMs: number): Promise<{ status: number; value: any }> {
|
|
303
345
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
304
346
|
try {
|
|
305
347
|
return await Promise.race([this.control(path, credential, body), new Promise<never>((_, reject) => {
|
|
@@ -368,7 +410,8 @@ export class WarmPodProvider {
|
|
|
368
410
|
warmReuseTimeLegs(): readonly WarmReuseTimeLegRow[] { return [...this.legs]; }
|
|
369
411
|
|
|
370
412
|
/** Prepare one named slot. A foreign or replaced Pod is never deleted. */
|
|
371
|
-
async ensureStandingWorker(candidate: WarmWorkerCandidate
|
|
413
|
+
async ensureStandingWorker(candidate: WarmWorkerCandidate,
|
|
414
|
+
onFailure?: (decision: WarmPoolFailureDecision) => void): Promise<boolean> {
|
|
372
415
|
const configured = this.candidates.find(row => row.host === candidate.host
|
|
373
416
|
&& row.spec.workerId === candidate.spec.workerId);
|
|
374
417
|
if (!configured) return false;
|
|
@@ -376,7 +419,7 @@ export class WarmPodProvider {
|
|
|
376
419
|
const inFlight = this.standingWorkers.get(key);
|
|
377
420
|
if (inFlight) return inFlight;
|
|
378
421
|
const preparation = withPrepareLease(configured, this.options.prepareLockDirectory,
|
|
379
|
-
() => this.prepareStandingWorker(configured, this.options.prepareAbortSignal), this.options.prepareAbortSignal,
|
|
422
|
+
() => this.prepareStandingWorker(configured, this.options.prepareAbortSignal, onFailure), this.options.prepareAbortSignal,
|
|
380
423
|
this.prepareFlock)
|
|
381
424
|
.then(result => result === true);
|
|
382
425
|
this.standingWorkers.set(key, preparation);
|
|
@@ -384,11 +427,32 @@ export class WarmPodProvider {
|
|
|
384
427
|
finally { if (this.standingWorkers.get(key) === preparation) this.standingWorkers.delete(key); }
|
|
385
428
|
}
|
|
386
429
|
|
|
387
|
-
private async prepareStandingWorker(candidate: WarmWorkerCandidate, signal?: AbortSignal
|
|
430
|
+
private async prepareStandingWorker(candidate: WarmWorkerCandidate, signal?: AbortSignal,
|
|
431
|
+
onFailure?: (decision: WarmPoolFailureDecision) => void): Promise<boolean> {
|
|
388
432
|
throwIfPrepareAborted(signal);
|
|
389
433
|
const startedAt = this.now();
|
|
390
434
|
const outcome: { preexisting: boolean; podUid: string | null } = { preexisting: false, podUid: null };
|
|
391
|
-
|
|
435
|
+
let decision: WarmPoolFailureDecision | undefined;
|
|
436
|
+
let ok = await this.coldPrepare(candidate, outcome, signal, value => { decision = value; });
|
|
437
|
+
// Reaping is only relevant when normal, bounded reconciliation has found
|
|
438
|
+
// this exact Pod and its ledger proves that its generation is fenced.
|
|
439
|
+
// Empty slots must take the ordinary cold-create path without a namespace
|
|
440
|
+
// dump or a preflight recovery request.
|
|
441
|
+
if (!ok && decision?.reason === 'quarantined-pod-needs-reap') {
|
|
442
|
+
try {
|
|
443
|
+
if (await this.reapQuarantinedWorker(candidate)) {
|
|
444
|
+
outcome.preexisting = false;
|
|
445
|
+
outcome.podUid = null;
|
|
446
|
+
decision = undefined;
|
|
447
|
+
ok = await this.coldPrepare(candidate, outcome, signal, value => { decision = value; });
|
|
448
|
+
}
|
|
449
|
+
} catch (error) {
|
|
450
|
+
debug.log('pod.warm', 'quarantine-reap-failed', { host: candidate.host,
|
|
451
|
+
workerId: candidate.spec.workerId,
|
|
452
|
+
reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' });
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
if (!ok && decision) onFailure?.(decision);
|
|
392
456
|
const totalDurationMs = this.now() - startedAt;
|
|
393
457
|
const gateFile = this.options.pairingGateFile;
|
|
394
458
|
// An unset gate file skips the measurement row entirely instead of recording an unpaired one.
|
|
@@ -398,8 +462,256 @@ export class WarmPodProvider {
|
|
|
398
462
|
return ok;
|
|
399
463
|
}
|
|
400
464
|
|
|
465
|
+
private readRotations(path: string): Record<string, string> {
|
|
466
|
+
if (!existsSync(path)) return {};
|
|
467
|
+
const info = lstatSync(path);
|
|
468
|
+
if (!info.isFile() || info.isSymbolicLink() || (info.mode & 0o077) !== 0)
|
|
469
|
+
throw new Error('unsafe warm rotation state');
|
|
470
|
+
const parsed: unknown = JSON.parse(readFileSync(path, 'utf8'));
|
|
471
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)
|
|
472
|
+
|| Object.entries(parsed).some(([slot, id]) => !slot.includes('/') || typeof id !== 'string' || !workerId(id)))
|
|
473
|
+
throw new Error('invalid warm rotation state');
|
|
474
|
+
return parsed as Record<string, string>;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
private writeRotation(slot: string, id: string): void {
|
|
478
|
+
const path = this.options.rotationStatePath;
|
|
479
|
+
if (!path) throw new Error('warm rotation state is not configured');
|
|
480
|
+
if (!this.prepareFlock) throw new Error('warm rotation state lock is unavailable');
|
|
481
|
+
const lockPath = `${path}.lock`;
|
|
482
|
+
const fd = openSync(lockPath, constants.O_CREAT | constants.O_RDWR | (constants.O_NOFOLLOW ?? 0), 0o600);
|
|
483
|
+
try {
|
|
484
|
+
const info = lstatSync(lockPath);
|
|
485
|
+
const uid = typeof process.getuid === 'function' ? process.getuid() : userInfo().uid;
|
|
486
|
+
if (!info.isFile() || info.isSymbolicLink() || info.uid !== uid || (info.mode & 0o077) !== 0
|
|
487
|
+
|| this.prepareFlock(fd, LOCK_EX) !== 0) throw new Error('unsafe warm rotation state lock');
|
|
488
|
+
const rotations = this.readRotations(path);
|
|
489
|
+
rotations[slot] = id;
|
|
490
|
+
const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
491
|
+
writeFileSync(temporary, JSON.stringify(rotations), { mode: 0o600, flag: 'wx' });
|
|
492
|
+
const temporaryFd = openSync(temporary, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
493
|
+
try { fsyncSync(temporaryFd); } finally { closeSync(temporaryFd); }
|
|
494
|
+
try {
|
|
495
|
+
renameSync(temporary, path);
|
|
496
|
+
const directoryFd = openSync(dirname(path), constants.O_RDONLY | (constants.O_DIRECTORY ?? 0));
|
|
497
|
+
try { fsyncSync(directoryFd); } finally { closeSync(directoryFd); }
|
|
498
|
+
}
|
|
499
|
+
catch (error) { rmSync(temporary, { force: true }); throw error; }
|
|
500
|
+
} finally {
|
|
501
|
+
this.prepareFlock(fd, LOCK_UN);
|
|
502
|
+
closeSync(fd);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
private async deletePodWithUid(spec: WarmPodSpec, uid: string): Promise<boolean> {
|
|
507
|
+
const deleteOptions: WarmPodDeleteOptions = {
|
|
508
|
+
apiVersion: 'v1', kind: 'DeleteOptions', preconditions: { uid },
|
|
509
|
+
};
|
|
510
|
+
const proxyUrl = this.options.kubectlProxyUrlForTest;
|
|
511
|
+
const path = `/api/v1/namespaces/${encodeURIComponent(spec.namespace)}/pods/${encodeURIComponent(spec.name)}`;
|
|
512
|
+
const acceptPath = `^${path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`;
|
|
513
|
+
// Keep the random-port, loopback proxy alive only for this request and expose only this Pod DELETE path.
|
|
514
|
+
const child = proxyUrl ? null : spawn('kubectl', ['--context', spec.kubectlContext, '-n', spec.namespace,
|
|
515
|
+
'proxy', '--address=127.0.0.1', '--port=0', `--accept-paths=${acceptPath}`],
|
|
516
|
+
{ detached: true, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
517
|
+
let output = '';
|
|
518
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
519
|
+
let exitPromise: Promise<void> | null = null;
|
|
520
|
+
try {
|
|
521
|
+
let baseUrl = proxyUrl;
|
|
522
|
+
if (!child) {
|
|
523
|
+
if (!baseUrl || !/^http:\/\/127\.0\.0\.1:\d+$/.test(baseUrl)) throw new Error('invalid test Kubernetes API URL');
|
|
524
|
+
} else {
|
|
525
|
+
const port = await new Promise<number>((resolve, reject) => {
|
|
526
|
+
timer = setTimeout(() => reject(new Error('kubectl proxy start timed out')), 10_000);
|
|
527
|
+
const inspect = (chunk: Buffer | string) => {
|
|
528
|
+
output += String(chunk);
|
|
529
|
+
const match = output.match(/127\.0\.0\.1:(\d+)/);
|
|
530
|
+
if (match) { if (timer) clearTimeout(timer); resolve(Number(match[1])); }
|
|
531
|
+
};
|
|
532
|
+
child.stdout.on('data', inspect);
|
|
533
|
+
child.stderr.on('data', inspect);
|
|
534
|
+
child.once('error', reject);
|
|
535
|
+
child.once('exit', (code, signal) => reject(new Error(`kubectl proxy exited (${code ?? signal})`)));
|
|
536
|
+
});
|
|
537
|
+
exitPromise = new Promise(resolve => child.once('exit', () => resolve()));
|
|
538
|
+
baseUrl = `http://127.0.0.1:${port}`;
|
|
539
|
+
}
|
|
540
|
+
const response = await (this.options.kubernetesFetch ?? this.fetcher)(`${baseUrl}${path}`, {
|
|
541
|
+
method: 'DELETE', headers: { 'content-type': 'application/json' },
|
|
542
|
+
body: JSON.stringify(deleteOptions), signal: AbortSignal.timeout(15_000),
|
|
543
|
+
});
|
|
544
|
+
return response.ok;
|
|
545
|
+
} finally {
|
|
546
|
+
if (timer) clearTimeout(timer);
|
|
547
|
+
child?.kill('SIGTERM');
|
|
548
|
+
if (exitPromise) await Promise.race([exitPromise, new Promise(resolve => setTimeout(resolve, 1_000))]);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/** Delete only a UID-pinned Pod whose ledger row proves quarantine and has no competing lease. */
|
|
553
|
+
private async reapQuarantinedWorker(candidate: WarmWorkerCandidate): Promise<boolean> {
|
|
554
|
+
const { host, tier, spec } = candidate;
|
|
555
|
+
const credential = this.options.machineTokens?.[host];
|
|
556
|
+
const observeRefusal = (reason: string) => debug.log('pod.warm', 'quarantine-reap-refused', {
|
|
557
|
+
host, workerId: spec.workerId, pod: spec.name, reason,
|
|
558
|
+
}, { level: 'warn' });
|
|
559
|
+
if (!token(credential) || !this.options.rotationStatePath) {
|
|
560
|
+
observeRefusal('rotation-state-or-credential-unavailable');
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
const kubectl = (args: readonly string[]) => this.options.kubectl
|
|
564
|
+
? this.options.kubectl(['--context', spec.kubectlContext, '-n', spec.namespace, ...args])
|
|
565
|
+
: spawnSync('kubectl', ['--context', spec.kubectlContext, '-n', spec.namespace, ...args],
|
|
566
|
+
{ encoding: 'utf8', timeout: 30_000, maxBuffer: 2 * 1024 * 1024 });
|
|
567
|
+
const parse = (result: { status: number | null; stdout: string }) => {
|
|
568
|
+
if (result.status !== 0) return null;
|
|
569
|
+
try { return JSON.parse(result.stdout) as any; } catch { return null; }
|
|
570
|
+
};
|
|
571
|
+
const readPods = (query: URLSearchParams) => {
|
|
572
|
+
if (this.options.kubectl) {
|
|
573
|
+
const selector = query.get('fieldSelector')
|
|
574
|
+
? [`--field-selector=${query.get('fieldSelector')}`]
|
|
575
|
+
: ['-l', query.get('labelSelector')!];
|
|
576
|
+
const continuation = query.get('continue');
|
|
577
|
+
return kubectl(['get', 'pods', ...selector, `--limit=${WARM_POD_LIST_PAGE_SIZE}`, '-o', 'json',
|
|
578
|
+
...(continuation ? [`--continue=${continuation}`] : [])]);
|
|
579
|
+
}
|
|
580
|
+
return kubectl(['get', '--raw', `/api/v1/namespaces/${encodeURIComponent(spec.namespace)}/pods?${query}`]);
|
|
581
|
+
};
|
|
582
|
+
const exactResult = readPods(new URLSearchParams({ fieldSelector: `metadata.name=${spec.name}`,
|
|
583
|
+
limit: String(WARM_POD_LIST_PAGE_SIZE) }));
|
|
584
|
+
const exact = parse(exactResult);
|
|
585
|
+
if (!Array.isArray(exact?.items) || exact.items.length > 1) {
|
|
586
|
+
observeRefusal('exact-pod-lookup-unavailable'); return false;
|
|
587
|
+
}
|
|
588
|
+
const exactPod = exact.items.find((pod: any) => pod?.metadata?.name === spec.name);
|
|
589
|
+
const listedPods: any[] = [];
|
|
590
|
+
const seenContinuations = new Set<string>();
|
|
591
|
+
let continuation: string | undefined;
|
|
592
|
+
for (let page = 0; page < WARM_POD_LIST_MAX_PAGES; page++) {
|
|
593
|
+
const query = new URLSearchParams({ labelSelector: 'elanous.kind=warm-worker',
|
|
594
|
+
limit: String(WARM_POD_LIST_PAGE_SIZE), ...(continuation ? { continue: continuation } : {}) });
|
|
595
|
+
const result = parse(readPods(query));
|
|
596
|
+
if (!Array.isArray(result?.items) || !result.metadata || typeof result.metadata !== 'object'
|
|
597
|
+
|| Array.isArray(result.metadata) || result.items.length > WARM_POD_LIST_PAGE_SIZE) {
|
|
598
|
+
observeRefusal('pod-list-page-unavailable'); return false;
|
|
599
|
+
}
|
|
600
|
+
listedPods.push(...result.items);
|
|
601
|
+
const next = result.metadata.continue;
|
|
602
|
+
if (next === undefined || next === '') { continuation = undefined; break; }
|
|
603
|
+
if (typeof next !== 'string' || seenContinuations.has(next)) {
|
|
604
|
+
observeRefusal('pod-list-page-token-invalid'); return false;
|
|
605
|
+
}
|
|
606
|
+
seenContinuations.add(next);
|
|
607
|
+
continuation = next;
|
|
608
|
+
}
|
|
609
|
+
if (continuation) { observeRefusal('pod-list-page-cap-reached'); return false; }
|
|
610
|
+
const matches = [exactPod, ...listedPods].filter((pod: any) => pod?.metadata?.name === spec.name
|
|
611
|
+
|| pod?.metadata?.annotations?.['elanous.dev/warm-worker-id'] === spec.workerId);
|
|
612
|
+
const uniqueMatches = [...new Map(matches.map((pod: any) => [`${pod?.metadata?.name}/${pod?.metadata?.uid ?? ''}`, pod])).values()];
|
|
613
|
+
if (uniqueMatches.length === 0) {
|
|
614
|
+
// Recover a crash after successful deletion but before the durable ID rotation.
|
|
615
|
+
const query = new URLSearchParams({ kind: 'pod-worker-lease' });
|
|
616
|
+
try {
|
|
617
|
+
const response = await this.controlBounded(`/v1/resources?${query}`, credential, undefined,
|
|
618
|
+
WARM_CONTROL_REQUEST_TIMEOUT_MS);
|
|
619
|
+
const rows = response.status === 200 && Array.isArray(response.value?.resources) ? response.value.resources : null;
|
|
620
|
+
const own = rows?.filter((row: any) => row?.name === spec.workerId) ?? [];
|
|
621
|
+
// Empty slots are the normal cold-create path, not failed quarantine recovery.
|
|
622
|
+
if (rows && own.length === 0) return false;
|
|
623
|
+
const prior = own.length === 1 ? own[0] : null;
|
|
624
|
+
const uid = prior?.attrs?.podUid;
|
|
625
|
+
if (rows && prior?.kind === 'pod-worker-lease' && prior.machine === host && typeof uid === 'string'
|
|
626
|
+
&& prior.attrs?.state === 'reaping'
|
|
627
|
+
&& !rows.some((row: any) => row !== prior && row?.attrs?.podUid === uid)) {
|
|
628
|
+
const nextId = `reap-${randomUUID().replaceAll('-', '')}`;
|
|
629
|
+
this.writeRotation(`${host}/${spec.name}`, nextId);
|
|
630
|
+
candidate.spec = { ...spec, workerId: nextId };
|
|
631
|
+
debug.log('pod.warm', 'quarantine-reap-recovered', { host, tier, workerId: spec.workerId,
|
|
632
|
+
nextWorkerId: nextId, pod: spec.name, podUid: uid });
|
|
633
|
+
return true;
|
|
634
|
+
}
|
|
635
|
+
} catch { /* Uncertain state remains unavailable. */ }
|
|
636
|
+
observeRefusal('pod-absence-or-quarantine-unproven');
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
if (uniqueMatches.length !== 1) { observeRefusal('ambiguous-pod-identity'); return false; }
|
|
640
|
+
const pod = uniqueMatches[0];
|
|
641
|
+
const manifest = warmWorkerPodManifest(spec);
|
|
642
|
+
const expectedMetadata = manifest.metadata as { labels: Record<string, string>; annotations: Record<string, string> };
|
|
643
|
+
const uid = pod?.metadata?.uid;
|
|
644
|
+
if (pod?.metadata?.name !== spec.name || pod?.metadata?.namespace !== spec.namespace
|
|
645
|
+
|| typeof uid !== 'string' || !uid || !pod.metadata.resourceVersion || pod.metadata.deletionTimestamp
|
|
646
|
+
|| pod.metadata.ownerReferences?.length || pod.spec?.nodeName !== spec.nodeName
|
|
647
|
+
|| pod.metadata.labels?.['elanous.kind'] !== 'warm-worker'
|
|
648
|
+
|| pod.metadata.annotations?.['elanous.dev/warm-worker-id'] !== spec.workerId
|
|
649
|
+
|| Object.entries(expectedMetadata.labels).some(([key, value]) => pod.metadata.labels?.[key] !== value)
|
|
650
|
+
|| Object.entries(expectedMetadata.annotations).some(([key, value]) => pod.metadata.annotations?.[key] !== value)
|
|
651
|
+
|| pod.spec?.containers?.[0]?.image !== spec.image
|
|
652
|
+
|| pod.spec?.containers?.[0]?.resources?.limits?.memory !== spec.memoryLimit) {
|
|
653
|
+
observeRefusal('pod-identity-unproven');
|
|
654
|
+
return false;
|
|
655
|
+
}
|
|
656
|
+
const query = new URLSearchParams({ kind: 'pod-worker-lease' });
|
|
657
|
+
let response: { status: number; value: any };
|
|
658
|
+
try { response = await this.controlBounded(`/v1/resources?${query}`, credential, undefined,
|
|
659
|
+
WARM_CONTROL_REQUEST_TIMEOUT_MS); }
|
|
660
|
+
catch { observeRefusal('lease-ledger-unavailable'); return false; }
|
|
661
|
+
const rows = response.status === 200 && Array.isArray(response.value?.resources) ? response.value.resources : null;
|
|
662
|
+
const own = rows?.filter((row: any) => row?.name === spec.workerId) ?? [];
|
|
663
|
+
const ledger = own.length === 1 ? own[0] : null;
|
|
664
|
+
if (!rows || !ledger || ledger.kind !== 'pod-worker-lease' || ledger.machine !== host
|
|
665
|
+
|| ledger.attrs?.podUid !== uid
|
|
666
|
+
|| !['quarantined', 'timeout-fenced', 'reaping'].includes(ledger.attrs?.state)
|
|
667
|
+
|| rows.some((row: any) => row !== ledger && row?.attrs?.podUid === uid)) {
|
|
668
|
+
observeRefusal('lease-or-identity-unproven');
|
|
669
|
+
return false;
|
|
670
|
+
}
|
|
671
|
+
let reservation: { status: number; value: any };
|
|
672
|
+
try {
|
|
673
|
+
reservation = await this.controlBounded(`/v1/leases/pod-worker/${encodeURIComponent(spec.workerId)}/reap`,
|
|
674
|
+
credential, { podUid: uid }, WARM_CONTROL_REQUEST_TIMEOUT_MS);
|
|
675
|
+
} catch { observeRefusal('reap-reservation-unavailable'); return false; }
|
|
676
|
+
if (reservation.status !== 200 || reservation.value?.attrs?.state !== 'reaping'
|
|
677
|
+
|| reservation.value?.attrs?.podUid !== uid) {
|
|
678
|
+
observeRefusal('reap-reservation-refused');
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
681
|
+
let deleted: boolean;
|
|
682
|
+
try { deleted = await this.deletePodWithUid(spec, uid); }
|
|
683
|
+
catch { deleted = false; }
|
|
684
|
+
if (!deleted) { observeRefusal('uid-precondition-delete-failed'); return false; }
|
|
685
|
+
const after = parse(readPods(new URLSearchParams({ fieldSelector: `metadata.name=${spec.name}`,
|
|
686
|
+
limit: String(WARM_POD_LIST_PAGE_SIZE) })));
|
|
687
|
+
if (!Array.isArray(after?.items) || after.items.some((row: any) => row?.metadata?.name === spec.name)) {
|
|
688
|
+
observeRefusal('pod-removal-unconfirmed');
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
const nextId = `reap-${randomUUID().replaceAll('-', '')}`;
|
|
692
|
+
const slot = `${host}/${spec.name}`;
|
|
693
|
+
this.writeRotation(slot, nextId);
|
|
694
|
+
candidate.spec = { ...spec, workerId: nextId };
|
|
695
|
+
debug.log('pod.warm', 'quarantine-reaped', { host, tier, workerId: spec.workerId,
|
|
696
|
+
nextWorkerId: nextId, pod: spec.name, podUid: uid });
|
|
697
|
+
return true;
|
|
698
|
+
}
|
|
699
|
+
|
|
401
700
|
private async coldPrepare(candidate: WarmWorkerCandidate,
|
|
402
|
-
outcome: { preexisting: boolean; podUid: string | null }, signal?: AbortSignal
|
|
701
|
+
outcome: { preexisting: boolean; podUid: string | null }, signal?: AbortSignal,
|
|
702
|
+
onFailure?: (decision: WarmPoolFailureDecision) => void): Promise<boolean> {
|
|
703
|
+
const credentialValues = [this.options.memberToken, ...Object.values(this.options.machineTokens ?? {}),
|
|
704
|
+
...Object.entries(process.env).filter(([name]) => /TOKEN|SECRET|PASSWORD|CREDENTIAL|API[_-]?KEY|AUTH/i.test(name))
|
|
705
|
+
.map(([, value]) => value ?? '')];
|
|
706
|
+
const fail = (stage: string, reason: string, stderr = ''): false => {
|
|
707
|
+
onFailure?.({ stage, reason, ...(stderr ? { stderr: safeWarmPoolStderr(stderr, credentialValues) } : {}) });
|
|
708
|
+
if (stage === 'pod-exact-lookup' || stage === 'pod-label-page') {
|
|
709
|
+
const listReason = reason === 'invalid-pod-list' || reason === 'invalid-pod-list-page'
|
|
710
|
+
? 'malformed-output' : reason === 'kubectl-output-limit' ? 'output-too-large' : 'kubectl-failed';
|
|
711
|
+
debug.log('pod.warm', 'warm-pod-list-skipped', { reason: listReason }, { level: 'warn' });
|
|
712
|
+
}
|
|
713
|
+
return false;
|
|
714
|
+
};
|
|
403
715
|
const configured = this.candidates.find(row => row.host === candidate.host
|
|
404
716
|
&& row.spec.workerId === candidate.spec.workerId && row.spec.cutSha === candidate.spec.cutSha
|
|
405
717
|
&& keyEquals(row.spec.key, candidate.spec.key));
|
|
@@ -410,19 +722,26 @@ export class WarmPodProvider {
|
|
|
410
722
|
if (!configured || !configured.spec.nodeName
|
|
411
723
|
|| (configured.spec.name !== prefix && !/^[1-9][0-9]*$/.test(suffix)
|
|
412
724
|
&& !/^(lite|standard)-[1-9][0-9]*$/.test(suffix))
|
|
413
|
-
|| candidate.spec.name !== configured.spec.name || candidate.spec.nodeName !== configured.spec.nodeName)
|
|
725
|
+
|| candidate.spec.name !== configured.spec.name || candidate.spec.nodeName !== configured.spec.nodeName)
|
|
726
|
+
return fail('slot-validation', 'candidate-mismatch');
|
|
414
727
|
throwIfPrepareAborted(signal);
|
|
415
728
|
const { spec, host } = configured;
|
|
416
729
|
const nodeName = spec.nodeName;
|
|
417
|
-
if (!nodeName) return
|
|
730
|
+
if (!nodeName) return fail('slot-validation', 'node-unpinned');
|
|
418
731
|
const credential = this.options.machineTokens?.[host];
|
|
419
|
-
if (!token(credential)) return
|
|
732
|
+
if (!token(credential)) return fail('slot-validation', 'machine-credential-unavailable');
|
|
420
733
|
const manifest = warmWorkerPodManifest(spec);
|
|
421
734
|
const metadata = manifest.metadata as { labels: Record<string, string>; annotations: Record<string, string> };
|
|
422
735
|
const template = manifest.spec as Record<string, unknown>;
|
|
423
|
-
const kubectl = (args: readonly string[], input?: string) => {
|
|
736
|
+
const kubectl = (args: readonly string[], input?: string): { status: number | null; stdout: string; stderr: string; error?: NodeJS.ErrnoException } => {
|
|
424
737
|
const argv = ['--context', spec.kubectlContext, '-n', spec.namespace, ...args];
|
|
425
|
-
if (this.options.kubectl)
|
|
738
|
+
if (this.options.kubectl) {
|
|
739
|
+
try { return this.options.kubectl(argv, input); }
|
|
740
|
+
catch (error) {
|
|
741
|
+
return { status: null, stdout: '', stderr: '', error: (error instanceof Error ? error : new Error(String(error))
|
|
742
|
+
) as NodeJS.ErrnoException };
|
|
743
|
+
}
|
|
744
|
+
}
|
|
426
745
|
const env = { ...process.env };
|
|
427
746
|
for (const name of ['HTTPS_PROXY', 'https_proxy', 'HTTP_PROXY', 'http_proxy', 'ALL_PROXY', 'all_proxy']) delete env[name];
|
|
428
747
|
// A new standing worker can spend more than five minutes in its cold init
|
|
@@ -431,12 +750,32 @@ export class WarmPodProvider {
|
|
|
431
750
|
const result = spawnSync('kubectl', argv, { encoding: 'utf8', input, env,
|
|
432
751
|
timeout: argv[4] === 'wait' ? (POD_READY_WAIT_SECONDS + 30) * 1000 : 360_000,
|
|
433
752
|
maxBuffer: 2 * 1024 * 1024 });
|
|
434
|
-
return { status: result.status, stdout: result.stdout ?? '', stderr: result.stderr ?? ''
|
|
753
|
+
return { status: result.status, stdout: result.stdout ?? '', stderr: result.stderr ?? '',
|
|
754
|
+
error: result.error };
|
|
435
755
|
};
|
|
436
756
|
const parse = (result: { status: number | null; stdout: string }) => {
|
|
437
757
|
if (result.status !== 0) return null;
|
|
438
758
|
try { return JSON.parse(result.stdout) as any; } catch { return null; }
|
|
439
759
|
};
|
|
760
|
+
const isOutputLimit = (result: { stderr?: string; error?: NodeJS.ErrnoException }) =>
|
|
761
|
+
result.error?.code === 'ENOBUFS'
|
|
762
|
+
|| /maxBuffer|ENOBUFS|buffer length/i.test([result.stderr, result.error?.message].filter(Boolean).join('\n'));
|
|
763
|
+
const podListPage = (selector: { name?: string; continuation?: string }) => {
|
|
764
|
+
if (this.options.kubectl) {
|
|
765
|
+
const args = selector.name
|
|
766
|
+
? ['get', 'pods', `--field-selector=metadata.name=${selector.name}`, '-o', 'json']
|
|
767
|
+
: ['get', 'pods', '-l', 'elanous.kind=warm-worker', `--limit=${WARM_POD_LIST_PAGE_SIZE}`, '-o', 'json',
|
|
768
|
+
...(selector.continuation ? [`--continue=${selector.continuation}`] : [])];
|
|
769
|
+
return kubectl(args);
|
|
770
|
+
}
|
|
771
|
+
const query = new URLSearchParams({
|
|
772
|
+
...(selector.name ? { fieldSelector: `metadata.name=${selector.name}` } : { labelSelector: 'elanous.kind=warm-worker' }),
|
|
773
|
+
limit: String(WARM_POD_LIST_PAGE_SIZE),
|
|
774
|
+
...(selector.continuation ? { continue: selector.continuation } : {}),
|
|
775
|
+
});
|
|
776
|
+
const path = `/api/v1/namespaces/${encodeURIComponent(spec.namespace)}/pods?${query}`;
|
|
777
|
+
return kubectl(['get', '--raw', path]);
|
|
778
|
+
};
|
|
440
779
|
const sameSpec = (actual: unknown, expected: unknown): boolean => {
|
|
441
780
|
if (Array.isArray(expected)) return Array.isArray(actual) && actual.length === expected.length
|
|
442
781
|
&& expected.every((item, index) => sameSpec(actual[index], item));
|
|
@@ -483,57 +822,69 @@ export class WarmPodProvider {
|
|
|
483
822
|
&& Object.entries(metadata.annotations).every(([key, value]) => pod.metadata.annotations?.[key] === value)
|
|
484
823
|
&& pod.spec?.nodeName === nodeName
|
|
485
824
|
&& sameSpec(normalizedSpec(pod.spec), template);
|
|
486
|
-
//
|
|
487
|
-
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
825
|
+
// Exact-name lookup finds the pinned candidate. A separate label query is
|
|
826
|
+
// paged so physical quota accounting never needs a namespace-wide dump.
|
|
827
|
+
const exactResult = kubectl(this.options.kubectl
|
|
828
|
+
? ['get', 'pods', `--field-selector=metadata.name=${spec.name}`, '-o', 'json']
|
|
829
|
+
: ['get', '--raw', `/api/v1/namespaces/${encodeURIComponent(spec.namespace)}/pods?${new URLSearchParams({
|
|
830
|
+
fieldSelector: `metadata.name=${spec.name}`, limit: String(WARM_POD_LIST_PAGE_SIZE),
|
|
831
|
+
})}`]);
|
|
832
|
+
if (exactResult.status !== 0) {
|
|
833
|
+
const detail = [exactResult.stderr, 'error' in exactResult ? exactResult.error : ''].filter(Boolean).join('\n');
|
|
834
|
+
return fail('pod-exact-lookup', isOutputLimit(exactResult)
|
|
835
|
+
? 'kubectl-output-limit' : 'kubectl-failed', detail);
|
|
836
|
+
}
|
|
837
|
+
const exact = parse(exactResult);
|
|
838
|
+
if (!Array.isArray(exact?.items)) return fail('pod-exact-lookup', 'invalid-pod-list');
|
|
839
|
+
const exactPod = exact.items.find((row: any) => row?.metadata?.name === spec.name);
|
|
840
|
+
const listed: any[] = [];
|
|
499
841
|
const seenContinuations = new Set<string>();
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
if (
|
|
512
|
-
|
|
842
|
+
let continuation: string | undefined;
|
|
843
|
+
for (let page = 0; page < WARM_POD_LIST_MAX_PAGES; page++) {
|
|
844
|
+
const pageResult = podListPage({ continuation });
|
|
845
|
+
if (pageResult.status !== 0) {
|
|
846
|
+
const detail = [pageResult.stderr, 'error' in pageResult ? pageResult.error : ''].filter(Boolean).join('\n');
|
|
847
|
+
const reason = isOutputLimit(pageResult) ? 'kubectl-output-limit' : 'kubectl-failed';
|
|
848
|
+
return fail('pod-label-page', reason, detail);
|
|
849
|
+
}
|
|
850
|
+
const result = parse(pageResult);
|
|
851
|
+
if (!Array.isArray(result?.items) || !result.metadata || typeof result.metadata !== 'object'
|
|
852
|
+
|| Array.isArray(result.metadata)) return fail('pod-label-page', 'invalid-pod-list-page');
|
|
853
|
+
if (result.items.length > WARM_POD_LIST_PAGE_SIZE) return fail('pod-label-page', 'pod-list-page-over-cap');
|
|
854
|
+
listed.push(...result.items);
|
|
855
|
+
const next = result.metadata?.continue;
|
|
856
|
+
if (next === undefined || next === '') { continuation = undefined; break; }
|
|
857
|
+
if (typeof next !== 'string' || seenContinuations.has(next)) return fail('pod-label-page', 'pod-list-page-token-invalid');
|
|
858
|
+
seenContinuations.add(next);
|
|
859
|
+
continuation = next;
|
|
513
860
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
861
|
+
if (continuation) return fail('pod-label-page', 'pod-list-page-cap-reached');
|
|
862
|
+
// Read the complete label-selected set: a Pod for this host on another node
|
|
863
|
+
// must block creation, and warm Pods on the pinned node count against quota.
|
|
864
|
+
const hostPods = [exactPod, ...listed].filter((row: any) => !!row && (row?.metadata?.name === spec.name
|
|
865
|
+
|| row?.metadata?.annotations?.['elanous.dev/warm-worker-id'] === spec.workerId));
|
|
866
|
+
const uniqueHostPods = [...new Map(hostPods.map((row: any) => [`${row?.metadata?.name}/${row?.metadata?.uid ?? ''}`, row])).values()];
|
|
867
|
+
if (uniqueHostPods.length > 1) return fail('pod-list', 'pod-list-over-cap');
|
|
868
|
+
let pod = uniqueHostPods[0];
|
|
519
869
|
const preexisting = !!pod;
|
|
520
870
|
outcome.preexisting = preexisting;
|
|
521
|
-
if (pod && !matches(pod)) return
|
|
871
|
+
if (pod && !matches(pod)) return fail('pod-validation', 'candidate-pod-mismatch');
|
|
522
872
|
// Old generations still occupy memory even when their ledger rows are quarantined.
|
|
523
873
|
// Count physical warm Pods on the pinned node before admitting a new one.
|
|
524
|
-
const physical =
|
|
874
|
+
const physical = listed.filter((row: any) => row?.metadata?.labels?.['elanous.kind'] === 'warm-worker'
|
|
525
875
|
&& row?.spec?.nodeName === nodeName && !row?.metadata?.deletionTimestamp
|
|
526
876
|
&& row?.status?.phase !== 'Succeeded' && row?.status?.phase !== 'Failed'
|
|
527
877
|
&& row?.spec?.containers?.[0]?.resources?.limits?.memory === spec.memoryLimit).length;
|
|
528
|
-
if (!preexisting && physical >=
|
|
878
|
+
if (!preexisting && physical >= this.quotaResolver(this.profile, host, configured.tier)) {
|
|
529
879
|
debug.log('pod.warm', 'pool-slot-refused', { host, tier: configured.tier,
|
|
530
880
|
workerId: spec.workerId, reason: 'physical-quota', physical }, { level: 'warn' });
|
|
531
|
-
return
|
|
881
|
+
return fail('pod-list', 'physical-quota');
|
|
532
882
|
}
|
|
533
883
|
const readLedger = async (): Promise<any[] | null> => {
|
|
534
884
|
try {
|
|
535
885
|
const query = new URLSearchParams({ kind: 'pod-worker-lease', machine: host, name: spec.workerId });
|
|
536
|
-
const ledger = await this.
|
|
886
|
+
const ledger = await this.controlBounded(`/v1/resources?${query}`, credential, undefined,
|
|
887
|
+
WARM_CONTROL_REQUEST_TIMEOUT_MS);
|
|
537
888
|
return ledger.status === 200 && Array.isArray(ledger.value?.resources)
|
|
538
889
|
&& ledger.value.resources.length <= 1 ? ledger.value.resources : null;
|
|
539
890
|
} catch { return null; }
|
|
@@ -542,9 +893,15 @@ export class WarmPodProvider {
|
|
|
542
893
|
// A worker ID already bound to another UID must not leave a newly created, unregistered Pod.
|
|
543
894
|
const before = await readLedger();
|
|
544
895
|
throwIfPrepareAborted(signal);
|
|
545
|
-
if (!before || before.length !== 0) return
|
|
546
|
-
|
|
547
|
-
if (
|
|
896
|
+
if (!before || before.length !== 0) return fail('lease-preflight', 'lease-unavailable');
|
|
897
|
+
const createResult = kubectl(['create', '-f', '-', '-o', 'json'], JSON.stringify(manifest));
|
|
898
|
+
if (createResult.status !== 0) {
|
|
899
|
+
const detail = [createResult.stderr, 'error' in createResult ? createResult.error : ''].filter(Boolean).join('\n');
|
|
900
|
+
const reason = /maxBuffer|ENOBUFS|buffer length/i.test(detail) ? 'kubectl-output-limit' : 'kubectl-create-denied';
|
|
901
|
+
return fail('pod-create', reason, detail);
|
|
902
|
+
}
|
|
903
|
+
pod = parse(createResult);
|
|
904
|
+
if (!matches(pod)) return fail('pod-create', 'created-pod-mismatch');
|
|
548
905
|
}
|
|
549
906
|
const uid = pod.metadata.uid as string;
|
|
550
907
|
outcome.podUid = uid;
|
|
@@ -562,15 +919,19 @@ export class WarmPodProvider {
|
|
|
562
919
|
const observed = parse(kubectl(['get', `pod/${spec.name}`, '-o', 'json']));
|
|
563
920
|
throwIfPrepareAborted(signal);
|
|
564
921
|
if (ready(observed)) { isReady = true; break; }
|
|
565
|
-
if (!matches(observed) || observed.metadata.uid !== uid) return
|
|
922
|
+
if (!matches(observed) || observed.metadata.uid !== uid) return fail('pod-ready', 'pod-replaced-or-mismatched');
|
|
566
923
|
if (this.now() < deadline) await waitForPrepareRetry(signal);
|
|
567
924
|
}
|
|
568
|
-
if (!isReady) return
|
|
925
|
+
if (!isReady) return fail('pod-ready', 'ready-deadline-exceeded');
|
|
569
926
|
// Re-read after create: a registration racing the preflight must fail before our POST.
|
|
570
927
|
const rows = await readLedger();
|
|
571
|
-
if (!rows) return
|
|
928
|
+
if (!rows) return fail('lease-registration', 'lease-read-failed');
|
|
572
929
|
const existing = rows[0];
|
|
573
930
|
const leaseTtlMs = this.options.leaseTtlMs ?? 30 * 60_000;
|
|
931
|
+
if (existing?.kind === 'pod-worker-lease' && existing.machine === host && existing.name === spec.workerId
|
|
932
|
+
&& existing.attrs?.podUid === uid
|
|
933
|
+
&& ['quarantined', 'timeout-fenced', 'reaping'].includes(existing.attrs?.state))
|
|
934
|
+
return fail('lease-registration', 'quarantined-pod-needs-reap');
|
|
574
935
|
if (existing) return preexisting && existing.kind === 'pod-worker-lease'
|
|
575
936
|
&& existing.machine === host && existing.name === spec.workerId
|
|
576
937
|
&& (existing.attrs?.state === 'idle' || existing.attrs?.state === 'leased')
|
|
@@ -582,7 +943,7 @@ export class WarmPodProvider {
|
|
|
582
943
|
&& existing.attrs?.key && keyEquals(existing.attrs.key, spec.key);
|
|
583
944
|
// An unregistered Pod could have been created by anyone with namespace access.
|
|
584
945
|
// Only this invocation's successful create response may become a new ledger row.
|
|
585
|
-
if (preexisting) return
|
|
946
|
+
if (preexisting) return fail('lease-registration', 'preexisting-pod-unregistered');
|
|
586
947
|
throwIfPrepareAborted(signal);
|
|
587
948
|
let registered: { status: number; value: any };
|
|
588
949
|
try {
|
|
@@ -590,11 +951,13 @@ export class WarmPodProvider {
|
|
|
590
951
|
workerId: spec.workerId, machine: host, podUid: uid, key: spec.key,
|
|
591
952
|
ttlMs: leaseTtlMs,
|
|
592
953
|
});
|
|
593
|
-
} catch { return
|
|
594
|
-
|
|
954
|
+
} catch { return fail('lease-registration', 'worker-registration-failed'); }
|
|
955
|
+
if (!(registered.status === 200 && registered.value?.name === spec.workerId
|
|
595
956
|
&& registered.value?.attrs?.state === 'idle'
|
|
596
957
|
&& registered.value?.attrs?.podUid === uid
|
|
597
|
-
&& !!registered.value.attrs.key && keyEquals(registered.value.attrs.key, spec.key)
|
|
958
|
+
&& !!registered.value.attrs.key && keyEquals(registered.value.attrs.key, spec.key)))
|
|
959
|
+
return fail('lease-registration', 'worker-registration-rejected');
|
|
960
|
+
return true;
|
|
598
961
|
}
|
|
599
962
|
|
|
600
963
|
async prepareWorker(workerId: string): Promise<boolean> {
|
|
@@ -609,10 +972,25 @@ export class WarmPodProvider {
|
|
|
609
972
|
let ready = 0;
|
|
610
973
|
for (const candidate of this.candidates) {
|
|
611
974
|
let prepared = false;
|
|
612
|
-
|
|
613
|
-
|
|
975
|
+
let decision: WarmPoolFailureDecision | undefined;
|
|
976
|
+
try { prepared = await this.ensureStandingWorker(candidate, value => { decision = value; }); }
|
|
977
|
+
catch (error) {
|
|
978
|
+
decision = { stage: 'prepare', reason: 'prepare-threw',
|
|
979
|
+
stderr: safeWarmPoolStderr(error instanceof Error ? error.message : String(error), [
|
|
980
|
+
this.options.memberToken, ...Object.values(this.options.machineTokens ?? {}),
|
|
981
|
+
]) };
|
|
982
|
+
}
|
|
614
983
|
if (prepared) ready++;
|
|
615
|
-
else
|
|
984
|
+
else {
|
|
985
|
+
failed.push(candidate.spec.workerId);
|
|
986
|
+
debug.log('pod.warm.provider', 'prepare-refused', { reason: decision?.reason ?? 'prepare-refused',
|
|
987
|
+
host: candidate.host, slot: candidate.spec.workerId }, { level: 'warn' });
|
|
988
|
+
debug.log('pod.warm', 'pool-reconcile-decision', { profile: this.profile,
|
|
989
|
+
host: candidate.host, tier: candidate.tier, workerId: candidate.spec.workerId,
|
|
990
|
+
stage: decision?.stage ?? 'prepare', reason: decision?.reason ?? 'prepare-refused',
|
|
991
|
+
...(decision?.stderr ? { stderr: decision.stderr } : {}),
|
|
992
|
+
}, { level: 'warn' });
|
|
993
|
+
}
|
|
616
994
|
debug.log('pod.warm', 'pool-slot', { profile: this.profile,
|
|
617
995
|
host: candidate.host, tier: candidate.tier, workerId: candidate.spec.workerId,
|
|
618
996
|
pod: candidate.spec.name, ready: prepared }, { level: prepared ? 'info' : 'warn' });
|
|
@@ -635,6 +1013,7 @@ export class WarmPodProvider {
|
|
|
635
1013
|
// host/key once so a busy first host cannot strand an idle second pool.
|
|
636
1014
|
const tried = new Set<string>();
|
|
637
1015
|
for (const candidate of matches) {
|
|
1016
|
+
if (input.deadlineAt <= this.now()) return lastFallback;
|
|
638
1017
|
const { spec, host } = candidate;
|
|
639
1018
|
const poolKey = `${host}/${spec.key.context}/${spec.key.imageDigest}/${spec.key.lockHash}/${spec.key.architecture}`;
|
|
640
1019
|
if (tried.has(poolKey)) continue;
|
|
@@ -642,7 +1021,8 @@ export class WarmPodProvider {
|
|
|
642
1021
|
const contextFailure = probeWarmPodContext(spec.kubectlContext, this.options.kubectl);
|
|
643
1022
|
if (contextFailure) { lastFallback = { command: null, transport: contextFailure }; continue; }
|
|
644
1023
|
let acquired: { status: number; value: any };
|
|
645
|
-
const
|
|
1024
|
+
const acquisitionCredential = this.options.machineTokens?.[host] ?? this.options.memberToken;
|
|
1025
|
+
const acquisition = this.control('/v1/leases/pod-worker', acquisitionCredential, {
|
|
646
1026
|
machine: host, key: spec.key, runId: input.runId, attemptId: input.attemptId,
|
|
647
1027
|
ttlMs: this.options.leaseTtlMs ?? 30 * 60_000,
|
|
648
1028
|
});
|
|
@@ -704,7 +1084,10 @@ export class WarmPodProvider {
|
|
|
704
1084
|
if (acquired.status === 409) {
|
|
705
1085
|
try {
|
|
706
1086
|
const query = new URLSearchParams({ kind: 'pod-worker-lease', machine: host, name: spec.workerId });
|
|
707
|
-
const
|
|
1087
|
+
const remainingMs = input.deadlineAt - this.now();
|
|
1088
|
+
if (remainingMs <= 0) return lastFallback;
|
|
1089
|
+
const observed = await this.controlBounded(`/v1/resources?${query}`, this.options.memberToken,
|
|
1090
|
+
undefined, Math.min(WARM_CONTROL_REQUEST_TIMEOUT_MS, remainingMs));
|
|
708
1091
|
const row = observed.status === 200 && Array.isArray(observed.value?.resources)
|
|
709
1092
|
? observed.value.resources.find((entry: any) => entry?.name === spec.workerId) : undefined;
|
|
710
1093
|
if (row?.attrs?.state === 'quarantined' || row?.attrs?.state === 'timeout-fenced') {
|
|
@@ -764,9 +1147,16 @@ export class WarmPodProvider {
|
|
|
764
1147
|
debug.log('pod.provenance', 'lease-start', { pod: leasedCandidate.spec.name, podUid: command.podUid,
|
|
765
1148
|
leaseId: command.leaseId, runId: command.runId, attemptId: command.attemptId,
|
|
766
1149
|
requestedAt: command.requestedAt, metadata: warmLeaseMetadata(command).annotations });
|
|
767
|
-
|
|
1150
|
+
let quarantineTimer: ReturnType<typeof setTimeout> | undefined;
|
|
1151
|
+
try {
|
|
1152
|
+
await Promise.race([this.quarantine(command, 'lease-response-after-deadline'), new Promise<never>((_, reject) => {
|
|
1153
|
+
quarantineTimer = setTimeout(() => reject(new Error('warm late-lease quarantine timed out')),
|
|
1154
|
+
WARM_CONTROL_REQUEST_TIMEOUT_MS);
|
|
1155
|
+
})]);
|
|
1156
|
+
}
|
|
768
1157
|
catch (error) { debug.log('pod.warm', 'late-lease-quarantine-failed', { workerId: command.workerId,
|
|
769
1158
|
leaseId: command.leaseId, generation: command.generation, error: String(error) }, { level: 'warn' }); }
|
|
1159
|
+
finally { if (quarantineTimer) clearTimeout(quarantineTimer); }
|
|
770
1160
|
return fallback('lease-unavailable');
|
|
771
1161
|
}
|
|
772
1162
|
// Acquisition is by pool key. A healthy sibling or replacement Pod must remain eligible.
|
|
@@ -937,7 +1327,7 @@ export function loadWarmPodProvider(path: string, pairingGateFileOrOptions?: str
|
|
|
937
1327
|
if (!pairingGateFile.trim()) throw new Error('warm provider pairing gate file must not be empty');
|
|
938
1328
|
options.pairingGateFile = pairingGateFile;
|
|
939
1329
|
}
|
|
940
|
-
return new WarmPodProvider({ ...options, ...runtimeOverrides });
|
|
1330
|
+
return new WarmPodProvider({ ...options, ...runtimeOverrides, rotationStatePath: options.rotationStatePath ?? `${path}.rotations` });
|
|
941
1331
|
}
|
|
942
1332
|
function currentProvider(): WarmPodProvider | null {
|
|
943
1333
|
if (configuredProvider) return configuredProvider;
|