elanous 0.2.43 → 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/{Om5xS9jpjSXJCeTNGYEi4 → HMXsxdy5MIIcHPMHv1wD4}/_buildManifest.js +1 -1
- package/apps/pwa/out/_next/static/chunks/{3557ac4a.c8931684475018ad.js → 08334415.f0e80896acbef722.js} +1 -1
- 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/118.2a89982fe4276880.js +23 -0
- 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/{3955.6922286237f2ac2b.js → 1306.967240ede9a0f757.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/1344.3b95f3bd1b77a3fe.js +94 -0
- package/apps/pwa/out/_next/static/chunks/{5628.6ddb2d5ffba4ff4c.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/{2573.387a707f6f9d720c.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/{7910-6f139bcafd238b9f.js → 1850-522de4ee34957210.js} +1 -1
- 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/{1406.a1dc2ab6f8449374.js → 1978.eeb60a606dc5deac.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/{3402.44b565b314f2263c.js → 2043.e233e3b5a3e790f5.js} +1 -1
- 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/{3694.e0099884345f0198.js → 2216.802962c04f40a26e.js} +1 -1
- 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/239.de075467b5523469.js +287 -0
- package/apps/pwa/out/_next/static/chunks/{1200.6a37c202f9f17e9a.js → 249.ba7051590d603c8f.js} +3 -3
- 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/{3935.16701e33f8982dc9.js → 2954.bbefc95d6b20e0d8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{aa57532f.510551854ff239b1.js → 2e0f720a.a0697c08e3806a60.js} +1 -1
- 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/{5930.8b4df3b46c32b05a.js → 3349.b5f09ee474d9533e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{5866.366966af47fdde5d.js → 3503.a47a9755fb4d97b0.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3506-5f429b3b7e1392b0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7900.096f455c7c1efc53.js → 3843.bad404d008893291.js} +2 -2
- 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/{8588.d1c6a2903727ce87.js → 4393.28fa5228d0eec217.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/4404.7281c847c6e10dfc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8062-7cd0bcc7f01455ba.js → 4415-b9d6a90960920a4a.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/{8313-dfa1021bb58a46d6.js → 447-5b304de1fe098f43.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4484-8ebea83d431e6fae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6944.309f4e3ea54d1537.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/{3072.01338851ca21a6f7.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/{8986.8e2e4efbfa655c59.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/4891.3f80ba517a3b2373.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8205.282ca4bb62d54a0a.js → 4998.810ca8833084caa9.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/4f98febc.ea5841d4d616bfb2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5789.2da35c258292e1e5.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/{9770.5f75bc6088b16109.js → 5439.3fec6dbce29ecb36.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{1313-19f737e0222c4444.js → 5612-074d9a5e00dfac6a.js} +1 -1
- 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/{3969.272ee7f9fd6c62e5.js → 5790.5eb06a653ebd8571.js} +1 -1
- 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/{6540.c7bef57ad8f90a87.js → 5989.7ecd362c5c44b747.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/{0ad6802e.ae7afb63fdcb4491.js → 5a34fb2f.52f1b4e1baebb48a.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/{0966f8a1.745fcd44f64a87ea.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/{2144.25b292396712196d.js → 6245.1182e007e9d230d9.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/{363bfab3.9623b9f89363541f.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/{d96660fb.cbc21a2a88539dbb.js → 6549dad0.2740b25e51bf8f05.js} +7 -7
- package/apps/pwa/out/_next/static/chunks/{5564.9e72532188f57586.js → 6881.9e7955847640c7fe.js} +4 -4
- 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/{3903.3ef4656c67572764.js → 7010.1bbd17a7f2b3e376.js} +1 -1
- 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/{2312.0c8b61e22d3136d7.js → 7679.32c40cdd14f20b4a.js} +1 -1
- 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/{4199.ce86b281fd0a60b7.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/8551.f762945c2cda2e95.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8635-57a9d8d9db70f4f1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{fb7d20d5-6892502b6ee39ca6.js → 86961958-4b0689a5048f756b.js} +1 -1
- 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/87f498e5-0881de09f2e44707.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8843.c3197f904c952b81.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{cb0f8df8.09a4a6be97e3fc5f.js → 892fd990.456232f326cc39c2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{837-81336ebaca8198a8.js → 8965-135a2c9eaa2464cb.js} +1 -1
- 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/{8637.90f92d5c8db4732c.js → 9398.1f56e3f17795f43f.js} +1 -1
- 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-0a815f34367b6db6.js → page-da787e8a10b8a82e.js} +1 -1
- 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/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +22 -22
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +22 -22
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +22 -22
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +21 -21
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +21 -21
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +22 -22
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +21 -21
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +22 -22
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +22 -22
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +21 -21
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +22 -22
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +22 -22
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +22 -22
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +22 -22
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +21 -21
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +21 -21
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +22 -22
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +22 -22
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +22 -22
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +21 -21
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +21 -21
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +21 -21
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +22 -22
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +22 -22
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +21 -21
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +21 -21
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +21 -21
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +21 -21
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +22 -22
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +22 -22
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +21 -21
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +22 -22
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +22 -22
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +22 -22
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +22 -22
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +22 -22
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +21 -21
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +22 -22
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +22 -22
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +22 -22
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +22 -22
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +22 -22
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +22 -22
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +22 -22
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +22 -22
- package/graphs/overlays/README.md +6 -0
- package/graphs/release/release-loop.yaml +33 -17
- package/graphs/units/verbs/make-doc.yaml +1 -0
- package/package.json +1 -1
- package/scripts/bench-metering-plan.ts +229 -0
- package/scripts/demo/demo1104-parallel-host.ts +56 -0
- package/scripts/demo/demo1104-parallel.ts +72 -0
- package/scripts/public-leak-baseline.txt +0 -1
- package/scripts/release-loop/docs-node.ts +4 -2
- package/scripts/release-loop/gate-image-pin.ts +116 -2
- package/scripts/release-loop/gate-node.ts +217 -25
- package/scripts/release-loop/gate-progress-sim.ts +73 -0
- package/scripts/release-loop/notes-repair.ts +18 -1
- package/scripts/release-loop/preflight.ts +188 -8
- 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/warm-control-forward.ts +178 -0
- package/scripts/retro-cycle.ts +30 -8
- package/scripts/rhythm/daily.ts +4 -1
- package/scripts/rhythm/linear-progress-sync.ts +28 -15
- package/scripts/ux-sim.ts +7 -1
- package/scripts/warm-pool-provider-config.ts +6 -1
- package/src/agent-mission/grok-auth-status.ts +78 -18
- package/src/agent-mission/mission-out.ts +2 -2
- package/src/agent-substrate/pr-reviewer.ts +147 -11
- package/src/agent-substrate/self-review-cli.ts +10 -2
- package/src/auto-research/research-fanout.ts +221 -0
- package/src/auto-research/research-turn.ts +282 -36
- package/src/autopilot/mission-codebase-gate.ts +7 -3
- package/src/cli/control-cli.ts +15 -6
- package/src/cli/log-event-names.ts +8 -0
- package/src/cli/setup-keys.ts +20 -8
- package/src/cli/tui-test-cli.ts +9 -5
- 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/research-policy.ts +7 -1
- package/src/core-turn/run-core-turn.ts +186 -39
- package/src/core-turn/types.ts +2 -0
- package/src/dashboard/classic-sidetab-mission-job.ts +113 -24
- package/src/dashboard/classic-sidetab-mission-pair.ts +3 -2
- package/src/dashboard/classic-sidetab-mission-trio.ts +35 -12
- package/src/dashboard/classic-sidetab-mission.ts +2 -0
- package/src/dashboard/index.ts +9 -1
- package/src/dashboard/sidetab-runtime.ts +2 -0
- package/src/dashboard/visible-mission-diagnostics.ts +16 -0
- 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/graph-runner/graph-cli.ts +24 -1
- package/src/graph-runner/graph-grow-llm.ts +3 -1
- package/src/graph-runner/runner.ts +226 -30
- package/src/harness/harness-cli-command.ts +4 -2
- package/src/harness/harness-pod-dispatch.ts +3 -0
- package/src/index.ts +2 -2
- package/src/llm/child-first-llm-observation.ts +112 -0
- package/src/llm/provider-credentials.ts +60 -4
- package/src/llm/provider-failure-evidence.ts +44 -0
- package/src/llm.ts +170 -11
- package/src/mcp/client.ts +7 -0
- package/src/mcp/proxy-runtime.ts +6 -1
- package/src/mcp/server.ts +54 -0
- package/src/native-tool-catalog.ts +2 -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/telegram-trigger-bot.ts +6 -6
- package/src/nexus/boot/register-mcp-clients.ts +114 -8
- package/src/nexus/index.ts +15 -0
- package/src/nexus/outbound/grokbot-adapter.ts +71 -0
- package/src/nexus/outbound/router.ts +33 -13
- package/src/oauth/codex-owner-mirror.ts +47 -10
- package/src/onboarding/first-run.ts +44 -6
- package/src/preview/terminal.ts +22 -7
- package/src/prompt-enhance/enhance.ts +10 -0
- package/src/pty-shell/pty-write-provenance.ts +21 -3
- package/src/pty-shell/registry.ts +9 -2
- package/src/release-loop/proposal-auto-place.ts +83 -0
- package/src/research/axis-fanout.ts +40 -5
- package/src/research/lenses/second-order.ts +312 -0
- package/src/self-dev/dev-cli.ts +3 -1
- package/src/self-dev/draft-sweep.ts +31 -10
- package/src/self-dev/draft-triage-rules.ts +60 -3
- package/src/self-dev/run-supervisor.ts +152 -2
- package/src/self-implement/goal-author.ts +208 -23
- package/src/self-implement/graph-overlay-yaml.ts +151 -0
- package/src/self-implement/graph-yaml.ts +68 -2
- package/src/self-implement/orchestrator.ts +62 -15
- package/src/self-implement/test-scenario-request.ts +37 -6
- package/src/session/chat.ts +2 -2
- package/src/session-runtime/index.ts +37 -5
- 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 +105 -47
- package/src/skills/tools/omni-search.ts +4 -2
- package/src/task-agent/parent-host.ts +19 -1
- package/src/task-agent/recall-inject.ts +98 -0
- package/src/task-agent/task-hand.ts +26 -7
- package/src/task-orchestrator/surfaces/pod-artifact-return.ts +19 -1
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +15 -0
- package/src/task-orchestrator/surfaces/pod-si-warm-host.ts +21 -9
- package/src/task-orchestrator/surfaces/pod-warm-worker-pod.ts +1 -1
- package/src/task-orchestrator/surfaces/pod-warm-worker-provider.ts +460 -96
- package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +45 -3
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +38 -8
- package/src/tool-hints/gate.ts +45 -14
- package/src/tool-hints/signals.ts +1 -1
- package/src/tool-hints/types.ts +2 -0
- package/src/tool-runtime/browser-hand-runtime.ts +11 -1
- package/src/tool-runtime/browser-runtime.ts +31 -18
- package/src/tool-runtime/elanous-linear-secretary-runtime.ts +152 -36
- package/src/tool-runtime/types.ts +2 -0
- package/src/tui/entry.ts +27 -7
- package/src/tui/sidetab/attach.ts +112 -0
- package/src/tui/sidetab/list.ts +3 -2
- package/src/tui-client/next-dashboard-runtime.ts +52 -0
- package/src/ux-sim/draft-inventory-builder.ts +2 -1
- package/src/ux-sim/linear-secretary.ts +4 -12
- package/src/ux-sim/mock-scenario-catalog.json +32 -29
- package/src/ux-sim/mock-scenario-catalog.ts +55 -3
- package/src/ux-sim/release-sim-rig.ts +62 -18
- package/src/ux-sim/service-key-setup.ts +63 -0
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/114.2b90424d1a3ce84c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1151-5c2bb8f002ca2dd6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1261-a616d5728e02e624.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1357.3bbef3976c37f94a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1362-20adddfaf662b0fa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1737-9491fc6ab29b9ec6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1803-4f91a8e7dcaf042c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1961.a81b12872c3e93a9.js +0 -23
- package/apps/pwa/out/_next/static/chunks/1970cf59.2d4d8ce5d6c81853.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1971-8890f3962ba7ee0a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2073-ba274e00e4f9efb4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2268-53861f339e0b65a2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/273.f156eee28f11af77.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2873.d2140e5a82546401.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3091.a5a4143dea33d197.js +0 -90
- package/apps/pwa/out/_next/static/chunks/3095.dc44586a5f83b86d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3113.d7472bbc27d580c7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3228-9650b6447b6fa288.js +0 -1
- package/apps/pwa/out/_next/static/chunks/360.ae9f9f4c33c395e8.js +0 -79
- package/apps/pwa/out/_next/static/chunks/3638.80412ea63aae8f3f.js +0 -286
- package/apps/pwa/out/_next/static/chunks/3744.54f87db52acfd1ab.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4311-6dd3144002cc0c2d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4426-2b230fad00c88930.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4445.7990c76b3751a918.js +0 -115
- package/apps/pwa/out/_next/static/chunks/4447-14c5161fa59166c7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4856-b0b8075bba8c53f4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4870-de370d65e73c0bb5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4885-2c989165c4b29fad.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4894.fec2ca9e99caefa2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5093.8f04b7f8cb899a95.js +0 -94
- package/apps/pwa/out/_next/static/chunks/5122-2bb177cebf89e19a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5154.5f56dc3902e3d275.js +0 -36
- package/apps/pwa/out/_next/static/chunks/529.64ff7257180b2ac9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5394.5dabeb0c62293a28.js +0 -93
- package/apps/pwa/out/_next/static/chunks/5461.08bf2404ff38ec85.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5474-22e08a85583209b2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5567-265df39ed8fe9dcb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5570-a94ae19811cb2046.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5574-b47c9ef2dceff1fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5730.43d7ccc09988e646.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5824.da9527b15a3221e5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5963-c4d8469c52a0828b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6001.ec4049ceb3c43418.js +0 -27
- package/apps/pwa/out/_next/static/chunks/6120-471f03db1e299c0c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6175-41479e643aca1878.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6291.a2de4f9787637075.js +0 -104
- package/apps/pwa/out/_next/static/chunks/6400-6699907c3537436a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6428.1f4327d6ae5fdf9a.js +0 -287
- package/apps/pwa/out/_next/static/chunks/644.02cb026fa44bcd0a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6466-6c87d60d3dd174fb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6773.d4264665ab31bec0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6822.3c789f1a8443e774.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7261-8ff69d3b2ee671fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7266.084c6bbc70035140.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7271.5c0100e7320aa856.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7429-bf5731358c8f2f68.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7464.01f7a0ae1215fbe7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7479.0fcad5f37eb64f0c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7483.427a2fe2049e388d.js +0 -148
- package/apps/pwa/out/_next/static/chunks/7502-be74718178e13e56.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7530.0889722b19572065.js +0 -24
- package/apps/pwa/out/_next/static/chunks/7645.5b2ee3ad45c55ea3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7788.7d1c79f1a8e18946.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7966.f71c8958d1637548.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8073.f6cf2f2c71aafbd0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8201.190b94088980f430.js +0 -74
- package/apps/pwa/out/_next/static/chunks/8447.7963352ef5f71b76.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8574-868e8129525ef6d9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/868.6e953be05bcf6dca.js +0 -41
- package/apps/pwa/out/_next/static/chunks/8706-e1fe0309343a2273.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8749-06384cdfc4520add.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8861-cc1fa1c7b920fa8e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8967-7dfebf710c9dec9d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8997-3c564dbb3b58b055.js +0 -1
- package/apps/pwa/out/_next/static/chunks/923.2034d10ae91e9c20.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9312.bc1b89c5d4368033.js +0 -1
- package/apps/pwa/out/_next/static/chunks/943.39ae5e82f4f1893f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9469.0500480de55fb205.js +0 -32
- package/apps/pwa/out/_next/static/chunks/9810-2543267050e58b82.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9870.8419702475254745.js +0 -8
- package/apps/pwa/out/_next/static/chunks/9941-376d1b74d51b01a4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9958.385434ab04241758.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-deea1450b17cb8dc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-70985b904f8650ef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-a2e15e33e56a4353.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-fc72ce2db12d6ca9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-be03e41f9436ebd9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-35a35999382da3f2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-3ff5b1e6ca88b848.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-a65101300a0ee5c1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-3aca5b6d67dba511.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-45b8ab0a71e61314.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-53046e282d4cda8e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-17809e60e8354c17.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-fd2442d347bf5898.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-e9d720d6fc082e21.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-6b8b4267bab0a677.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-deea1450b17cb8dc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-8bcf6233ac7c17af.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-7cc25591cfc2126f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-053e871f35c9beba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-eff819f0c2d5e084.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-4d7e9d8db5274a87.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-2b2d8f5aa5f69ad4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-0b8f92c5062e68b1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1bd9d0fdac2ced3d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-9ee28f7df07428ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-44b7a1ff468c6648.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-cbc6523711dc1b65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-fdad47899c2695be.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-79a8b5fee4c20871.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-03e8ac9606aeaffb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-4cd55c55ec287e2f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-db17b4305404161e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-30041d848350fd02.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-c540da022dbf86b0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-9399efc538da057f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-ca2d09128c585ca4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-0e2635aecd343038.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-ef7f4399b6b7726e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-deea1450b17cb8dc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-caf530535b875468.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-eaa20c1543a5fd8e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-c541eb1b75120f61.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-ab5e213eda9479f4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-e858ba0ed9393b68.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-1638ee9dd6e0582f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-4cf5a399109391f2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b4396321238798ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-cb72bfd21006781a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-6135d0242afbc434.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-a1dccf907eddb856.js +0 -1
- package/apps/pwa/out/_next/static/chunks/c7945435-b44d4dbe90ba0dcb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/ea514caa.05a24f78d9dffe5f.js +0 -122
- package/apps/pwa/out/_next/static/chunks/framework-e8a911e19b1be41f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-7022f418fabba021.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-e5f15df755756a1e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-4525f91e36940c9b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-140f0e519c085b0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-8f22ef63dcb865b1.js +0 -1
- /package/apps/pwa/out/_next/static/{Om5xS9jpjSXJCeTNGYEi4 → 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;
|
|
@@ -67,6 +74,14 @@ export interface WarmPodProviderOptions {
|
|
|
67
74
|
};
|
|
68
75
|
/** Private directory for cross-process prepare leases; defaults to a per-user directory under tmpdir(). */
|
|
69
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;
|
|
70
85
|
/** Cancels a prepare waiting for another process to finish the same host/worker slot. */
|
|
71
86
|
prepareAbortSignal?: AbortSignal;
|
|
72
87
|
/** Injects the prepare-lease flock binding. Omitted keeps this platform's binding; null/false declares it unsupported. */
|
|
@@ -74,7 +89,7 @@ export interface WarmPodProviderOptions {
|
|
|
74
89
|
/** Gate file this pool serves, used to bind cold prepare legs to their 1-file gate measurement key. */
|
|
75
90
|
pairingGateFile?: string;
|
|
76
91
|
}
|
|
77
|
-
type WarmPodProviderRuntimeOverrides = Pick<WarmPodProviderOptions, 'fetch' | 'kubectl' | 'prepareLockDirectory'>;
|
|
92
|
+
type WarmPodProviderRuntimeOverrides = Pick<WarmPodProviderOptions, 'controlUrl' | 'fetch' | 'kubectl' | 'prepareLockDirectory'>;
|
|
78
93
|
export interface WarmPodProviderLoadOptions {
|
|
79
94
|
pairingGateFile?: string;
|
|
80
95
|
runtimeOverrides?: Partial<WarmPodProviderRuntimeOverrides>;
|
|
@@ -86,6 +101,19 @@ export interface WarmPoolReconcileResult {
|
|
|
86
101
|
ready: number;
|
|
87
102
|
failed: readonly string[];
|
|
88
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
|
+
}
|
|
89
117
|
|
|
90
118
|
/** One leg of a read-only warm/cold prepare-time measurement pair, bound to one 1-file gate key. */
|
|
91
119
|
export interface WarmPreparePairLeg {
|
|
@@ -229,7 +257,7 @@ async function withPrepareLease<T>(candidate: WarmWorkerCandidate, directory: st
|
|
|
229
257
|
export class WarmPodProvider {
|
|
230
258
|
private readonly fetcher: (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
231
259
|
private readonly now: () => number;
|
|
232
|
-
private readonly candidates:
|
|
260
|
+
private readonly candidates: WarmWorkerCandidate[];
|
|
233
261
|
private readonly profile: WarmPoolProfile;
|
|
234
262
|
/** This process's own legs, echoed on CLI stdout. Pairing never reads this — see readWarmReuseTimePairs. */
|
|
235
263
|
private readonly legs: WarmReuseTimeLegRow[] = [];
|
|
@@ -243,20 +271,24 @@ export class WarmPodProvider {
|
|
|
243
271
|
private readonly standingWorkers = new Map<string, Promise<boolean>>();
|
|
244
272
|
/** This instance's prepare-lease flock binding; null means the platform cannot lock and every slot fails. */
|
|
245
273
|
private readonly prepareFlock: WarmPrepareFlock | null;
|
|
274
|
+
private readonly quotaResolver: typeof resolveWarmPoolQuota;
|
|
246
275
|
constructor(private readonly options: WarmPodProviderOptions) {
|
|
247
276
|
const url = new URL(options.controlUrl);
|
|
248
277
|
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.search || url.hash
|
|
249
278
|
|| url.pathname !== '/' || !token(options.memberToken)) throw new Error('invalid warm control address');
|
|
279
|
+
this.quotaResolver = options.quotaResolverForTest ?? resolveWarmPoolQuota;
|
|
250
280
|
const profile = resolveWarmPoolProfile(options.profileConfig ?? getUserConfig());
|
|
251
281
|
this.profile = profile;
|
|
252
282
|
const templates = options.poolTemplates ?? [];
|
|
253
283
|
if (new Set(templates.map(candidate => `${candidate.host}/${candidate.tier}`)).size !== templates.length)
|
|
254
284
|
throw new Error('duplicate warm pool template');
|
|
255
285
|
const expanded = templates.flatMap(candidate => {
|
|
256
|
-
const quota =
|
|
286
|
+
const quota = this.quotaResolver(profile, candidate.host, candidate.tier);
|
|
257
287
|
if (!quota || !candidate.spec.nodeName) throw new Error('unmeasured warm pool template');
|
|
258
|
-
|
|
259
|
-
|
|
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: {
|
|
260
292
|
...candidate.spec,
|
|
261
293
|
name: `warm-${candidate.host}-${candidate.tier}-${index + 1}`,
|
|
262
294
|
// The template worker ID is a generation token. Rotate it when a slot is quarantined.
|
|
@@ -264,9 +296,16 @@ export class WarmPodProvider {
|
|
|
264
296
|
},
|
|
265
297
|
}));
|
|
266
298
|
});
|
|
267
|
-
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
|
+
}
|
|
268
307
|
if (this.candidates.some(candidate => !workerId(candidate.spec.workerId)
|
|
269
|
-
|| !candidate.host.trim() ||
|
|
308
|
+
|| !candidate.host.trim() || this.quotaResolver(profile, candidate.host, candidate.tier) < 1
|
|
270
309
|
|| candidate.spec.memoryLimit !== (candidate.tier === 'lite' ? '2Gi' : '16Gi')
|
|
271
310
|
|| !warmWorkerPodManifest(candidate.spec))) throw new Error('invalid warm candidate');
|
|
272
311
|
if (new Set(this.candidates.map(candidate => candidate.spec.workerId)).size !== this.candidates.length)
|
|
@@ -279,7 +318,7 @@ export class WarmPodProvider {
|
|
|
279
318
|
names.add(identity);
|
|
280
319
|
const slot = `${candidate.host}/${candidate.tier}`;
|
|
281
320
|
const count = (counts.get(slot) ?? 0) + 1;
|
|
282
|
-
if (count >
|
|
321
|
+
if (count > this.quotaResolver(profile, candidate.host, candidate.tier))
|
|
283
322
|
throw new Error('warm candidate quota exceeded');
|
|
284
323
|
counts.set(slot, count);
|
|
285
324
|
}
|
|
@@ -301,7 +340,8 @@ export class WarmPodProvider {
|
|
|
301
340
|
return { status: response.status, value };
|
|
302
341
|
}
|
|
303
342
|
|
|
304
|
-
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 }> {
|
|
305
345
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
306
346
|
try {
|
|
307
347
|
return await Promise.race([this.control(path, credential, body), new Promise<never>((_, reject) => {
|
|
@@ -370,7 +410,8 @@ export class WarmPodProvider {
|
|
|
370
410
|
warmReuseTimeLegs(): readonly WarmReuseTimeLegRow[] { return [...this.legs]; }
|
|
371
411
|
|
|
372
412
|
/** Prepare one named slot. A foreign or replaced Pod is never deleted. */
|
|
373
|
-
async ensureStandingWorker(candidate: WarmWorkerCandidate
|
|
413
|
+
async ensureStandingWorker(candidate: WarmWorkerCandidate,
|
|
414
|
+
onFailure?: (decision: WarmPoolFailureDecision) => void): Promise<boolean> {
|
|
374
415
|
const configured = this.candidates.find(row => row.host === candidate.host
|
|
375
416
|
&& row.spec.workerId === candidate.spec.workerId);
|
|
376
417
|
if (!configured) return false;
|
|
@@ -378,7 +419,7 @@ export class WarmPodProvider {
|
|
|
378
419
|
const inFlight = this.standingWorkers.get(key);
|
|
379
420
|
if (inFlight) return inFlight;
|
|
380
421
|
const preparation = withPrepareLease(configured, this.options.prepareLockDirectory,
|
|
381
|
-
() => this.prepareStandingWorker(configured, this.options.prepareAbortSignal), this.options.prepareAbortSignal,
|
|
422
|
+
() => this.prepareStandingWorker(configured, this.options.prepareAbortSignal, onFailure), this.options.prepareAbortSignal,
|
|
382
423
|
this.prepareFlock)
|
|
383
424
|
.then(result => result === true);
|
|
384
425
|
this.standingWorkers.set(key, preparation);
|
|
@@ -386,11 +427,32 @@ export class WarmPodProvider {
|
|
|
386
427
|
finally { if (this.standingWorkers.get(key) === preparation) this.standingWorkers.delete(key); }
|
|
387
428
|
}
|
|
388
429
|
|
|
389
|
-
private async prepareStandingWorker(candidate: WarmWorkerCandidate, signal?: AbortSignal
|
|
430
|
+
private async prepareStandingWorker(candidate: WarmWorkerCandidate, signal?: AbortSignal,
|
|
431
|
+
onFailure?: (decision: WarmPoolFailureDecision) => void): Promise<boolean> {
|
|
390
432
|
throwIfPrepareAborted(signal);
|
|
391
433
|
const startedAt = this.now();
|
|
392
434
|
const outcome: { preexisting: boolean; podUid: string | null } = { preexisting: false, podUid: null };
|
|
393
|
-
|
|
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);
|
|
394
456
|
const totalDurationMs = this.now() - startedAt;
|
|
395
457
|
const gateFile = this.options.pairingGateFile;
|
|
396
458
|
// An unset gate file skips the measurement row entirely instead of recording an unpaired one.
|
|
@@ -400,8 +462,256 @@ export class WarmPodProvider {
|
|
|
400
462
|
return ok;
|
|
401
463
|
}
|
|
402
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
|
+
|
|
403
700
|
private async coldPrepare(candidate: WarmWorkerCandidate,
|
|
404
|
-
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
|
+
};
|
|
405
715
|
const configured = this.candidates.find(row => row.host === candidate.host
|
|
406
716
|
&& row.spec.workerId === candidate.spec.workerId && row.spec.cutSha === candidate.spec.cutSha
|
|
407
717
|
&& keyEquals(row.spec.key, candidate.spec.key));
|
|
@@ -412,19 +722,26 @@ export class WarmPodProvider {
|
|
|
412
722
|
if (!configured || !configured.spec.nodeName
|
|
413
723
|
|| (configured.spec.name !== prefix && !/^[1-9][0-9]*$/.test(suffix)
|
|
414
724
|
&& !/^(lite|standard)-[1-9][0-9]*$/.test(suffix))
|
|
415
|
-
|| 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');
|
|
416
727
|
throwIfPrepareAborted(signal);
|
|
417
728
|
const { spec, host } = configured;
|
|
418
729
|
const nodeName = spec.nodeName;
|
|
419
|
-
if (!nodeName) return
|
|
730
|
+
if (!nodeName) return fail('slot-validation', 'node-unpinned');
|
|
420
731
|
const credential = this.options.machineTokens?.[host];
|
|
421
|
-
if (!token(credential)) return
|
|
732
|
+
if (!token(credential)) return fail('slot-validation', 'machine-credential-unavailable');
|
|
422
733
|
const manifest = warmWorkerPodManifest(spec);
|
|
423
734
|
const metadata = manifest.metadata as { labels: Record<string, string>; annotations: Record<string, string> };
|
|
424
735
|
const template = manifest.spec as Record<string, unknown>;
|
|
425
|
-
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 } => {
|
|
426
737
|
const argv = ['--context', spec.kubectlContext, '-n', spec.namespace, ...args];
|
|
427
|
-
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
|
+
}
|
|
428
745
|
const env = { ...process.env };
|
|
429
746
|
for (const name of ['HTTPS_PROXY', 'https_proxy', 'HTTP_PROXY', 'http_proxy', 'ALL_PROXY', 'all_proxy']) delete env[name];
|
|
430
747
|
// A new standing worker can spend more than five minutes in its cold init
|
|
@@ -440,6 +757,25 @@ export class WarmPodProvider {
|
|
|
440
757
|
if (result.status !== 0) return null;
|
|
441
758
|
try { return JSON.parse(result.stdout) as any; } catch { return null; }
|
|
442
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
|
+
};
|
|
443
779
|
const sameSpec = (actual: unknown, expected: unknown): boolean => {
|
|
444
780
|
if (Array.isArray(expected)) return Array.isArray(actual) && actual.length === expected.length
|
|
445
781
|
&& expected.every((item, index) => sameSpec(actual[index], item));
|
|
@@ -486,80 +822,69 @@ export class WarmPodProvider {
|
|
|
486
822
|
&& Object.entries(metadata.annotations).every(([key, value]) => pod.metadata.annotations?.[key] === value)
|
|
487
823
|
&& pod.spec?.nodeName === nodeName
|
|
488
824
|
&& sameSpec(normalizedSpec(pod.spec), template);
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
catch (error) {
|
|
501
|
-
return skipDiscovery(kubectlErrorCode(error) === 'ENOBUFS' ? 'output-too-large' : 'kubectl-failed');
|
|
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);
|
|
502
836
|
}
|
|
503
|
-
|
|
504
|
-
if (
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
|| Array.isArray(namedList.metadata)) return skipDiscovery('malformed-output');
|
|
508
|
-
const namedMatches = namedList.items.filter((row: any) => row?.metadata?.name === spec.name);
|
|
509
|
-
if (namedMatches.length > 1) return false;
|
|
510
|
-
const namedPod = namedMatches[0] ?? null;
|
|
511
|
-
|
|
512
|
-
// Kubectl's continuation token is consumed explicitly so no partial page can
|
|
513
|
-
// authorize a create. A hard page cap turns a non-terminating listing into failure.
|
|
514
|
-
const warmPods: any[] = [];
|
|
515
|
-
let continuation = '';
|
|
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[] = [];
|
|
516
841
|
const seenContinuations = new Set<string>();
|
|
517
|
-
let
|
|
518
|
-
for (let page = 0; page <
|
|
519
|
-
const
|
|
520
|
-
if (
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
return skipDiscovery(kubectlErrorCode(error) === 'ENOBUFS' ? 'output-too-large' : 'kubectl-failed');
|
|
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);
|
|
525
849
|
}
|
|
526
|
-
|
|
527
|
-
if (result.
|
|
528
|
-
|
|
529
|
-
if (
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
if (seenContinuations.has(continuation) || page === 99) return skipDiscovery('malformed-output');
|
|
537
|
-
seenContinuations.add(continuation);
|
|
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;
|
|
538
860
|
}
|
|
539
|
-
if (
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
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];
|
|
545
869
|
const preexisting = !!pod;
|
|
546
870
|
outcome.preexisting = preexisting;
|
|
547
|
-
if (pod && !matches(pod)) return
|
|
871
|
+
if (pod && !matches(pod)) return fail('pod-validation', 'candidate-pod-mismatch');
|
|
548
872
|
// Old generations still occupy memory even when their ledger rows are quarantined.
|
|
549
873
|
// Count physical warm Pods on the pinned node before admitting a new one.
|
|
550
|
-
const physical =
|
|
874
|
+
const physical = listed.filter((row: any) => row?.metadata?.labels?.['elanous.kind'] === 'warm-worker'
|
|
551
875
|
&& row?.spec?.nodeName === nodeName && !row?.metadata?.deletionTimestamp
|
|
552
876
|
&& row?.status?.phase !== 'Succeeded' && row?.status?.phase !== 'Failed'
|
|
553
877
|
&& row?.spec?.containers?.[0]?.resources?.limits?.memory === spec.memoryLimit).length;
|
|
554
|
-
if (!preexisting && physical >=
|
|
878
|
+
if (!preexisting && physical >= this.quotaResolver(this.profile, host, configured.tier)) {
|
|
555
879
|
debug.log('pod.warm', 'pool-slot-refused', { host, tier: configured.tier,
|
|
556
880
|
workerId: spec.workerId, reason: 'physical-quota', physical }, { level: 'warn' });
|
|
557
|
-
return
|
|
881
|
+
return fail('pod-list', 'physical-quota');
|
|
558
882
|
}
|
|
559
883
|
const readLedger = async (): Promise<any[] | null> => {
|
|
560
884
|
try {
|
|
561
885
|
const query = new URLSearchParams({ kind: 'pod-worker-lease', machine: host, name: spec.workerId });
|
|
562
|
-
const ledger = await this.
|
|
886
|
+
const ledger = await this.controlBounded(`/v1/resources?${query}`, credential, undefined,
|
|
887
|
+
WARM_CONTROL_REQUEST_TIMEOUT_MS);
|
|
563
888
|
return ledger.status === 200 && Array.isArray(ledger.value?.resources)
|
|
564
889
|
&& ledger.value.resources.length <= 1 ? ledger.value.resources : null;
|
|
565
890
|
} catch { return null; }
|
|
@@ -568,9 +893,15 @@ export class WarmPodProvider {
|
|
|
568
893
|
// A worker ID already bound to another UID must not leave a newly created, unregistered Pod.
|
|
569
894
|
const before = await readLedger();
|
|
570
895
|
throwIfPrepareAborted(signal);
|
|
571
|
-
if (!before || before.length !== 0) return
|
|
572
|
-
|
|
573
|
-
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');
|
|
574
905
|
}
|
|
575
906
|
const uid = pod.metadata.uid as string;
|
|
576
907
|
outcome.podUid = uid;
|
|
@@ -588,15 +919,19 @@ export class WarmPodProvider {
|
|
|
588
919
|
const observed = parse(kubectl(['get', `pod/${spec.name}`, '-o', 'json']));
|
|
589
920
|
throwIfPrepareAborted(signal);
|
|
590
921
|
if (ready(observed)) { isReady = true; break; }
|
|
591
|
-
if (!matches(observed) || observed.metadata.uid !== uid) return
|
|
922
|
+
if (!matches(observed) || observed.metadata.uid !== uid) return fail('pod-ready', 'pod-replaced-or-mismatched');
|
|
592
923
|
if (this.now() < deadline) await waitForPrepareRetry(signal);
|
|
593
924
|
}
|
|
594
|
-
if (!isReady) return
|
|
925
|
+
if (!isReady) return fail('pod-ready', 'ready-deadline-exceeded');
|
|
595
926
|
// Re-read after create: a registration racing the preflight must fail before our POST.
|
|
596
927
|
const rows = await readLedger();
|
|
597
|
-
if (!rows) return
|
|
928
|
+
if (!rows) return fail('lease-registration', 'lease-read-failed');
|
|
598
929
|
const existing = rows[0];
|
|
599
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');
|
|
600
935
|
if (existing) return preexisting && existing.kind === 'pod-worker-lease'
|
|
601
936
|
&& existing.machine === host && existing.name === spec.workerId
|
|
602
937
|
&& (existing.attrs?.state === 'idle' || existing.attrs?.state === 'leased')
|
|
@@ -608,7 +943,7 @@ export class WarmPodProvider {
|
|
|
608
943
|
&& existing.attrs?.key && keyEquals(existing.attrs.key, spec.key);
|
|
609
944
|
// An unregistered Pod could have been created by anyone with namespace access.
|
|
610
945
|
// Only this invocation's successful create response may become a new ledger row.
|
|
611
|
-
if (preexisting) return
|
|
946
|
+
if (preexisting) return fail('lease-registration', 'preexisting-pod-unregistered');
|
|
612
947
|
throwIfPrepareAborted(signal);
|
|
613
948
|
let registered: { status: number; value: any };
|
|
614
949
|
try {
|
|
@@ -616,11 +951,13 @@ export class WarmPodProvider {
|
|
|
616
951
|
workerId: spec.workerId, machine: host, podUid: uid, key: spec.key,
|
|
617
952
|
ttlMs: leaseTtlMs,
|
|
618
953
|
});
|
|
619
|
-
} catch { return
|
|
620
|
-
|
|
954
|
+
} catch { return fail('lease-registration', 'worker-registration-failed'); }
|
|
955
|
+
if (!(registered.status === 200 && registered.value?.name === spec.workerId
|
|
621
956
|
&& registered.value?.attrs?.state === 'idle'
|
|
622
957
|
&& registered.value?.attrs?.podUid === uid
|
|
623
|
-
&& !!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;
|
|
624
961
|
}
|
|
625
962
|
|
|
626
963
|
async prepareWorker(workerId: string): Promise<boolean> {
|
|
@@ -635,10 +972,25 @@ export class WarmPodProvider {
|
|
|
635
972
|
let ready = 0;
|
|
636
973
|
for (const candidate of this.candidates) {
|
|
637
974
|
let prepared = false;
|
|
638
|
-
|
|
639
|
-
|
|
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
|
+
}
|
|
640
983
|
if (prepared) ready++;
|
|
641
|
-
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
|
+
}
|
|
642
994
|
debug.log('pod.warm', 'pool-slot', { profile: this.profile,
|
|
643
995
|
host: candidate.host, tier: candidate.tier, workerId: candidate.spec.workerId,
|
|
644
996
|
pod: candidate.spec.name, ready: prepared }, { level: prepared ? 'info' : 'warn' });
|
|
@@ -661,6 +1013,7 @@ export class WarmPodProvider {
|
|
|
661
1013
|
// host/key once so a busy first host cannot strand an idle second pool.
|
|
662
1014
|
const tried = new Set<string>();
|
|
663
1015
|
for (const candidate of matches) {
|
|
1016
|
+
if (input.deadlineAt <= this.now()) return lastFallback;
|
|
664
1017
|
const { spec, host } = candidate;
|
|
665
1018
|
const poolKey = `${host}/${spec.key.context}/${spec.key.imageDigest}/${spec.key.lockHash}/${spec.key.architecture}`;
|
|
666
1019
|
if (tried.has(poolKey)) continue;
|
|
@@ -668,7 +1021,8 @@ export class WarmPodProvider {
|
|
|
668
1021
|
const contextFailure = probeWarmPodContext(spec.kubectlContext, this.options.kubectl);
|
|
669
1022
|
if (contextFailure) { lastFallback = { command: null, transport: contextFailure }; continue; }
|
|
670
1023
|
let acquired: { status: number; value: any };
|
|
671
|
-
const
|
|
1024
|
+
const acquisitionCredential = this.options.machineTokens?.[host] ?? this.options.memberToken;
|
|
1025
|
+
const acquisition = this.control('/v1/leases/pod-worker', acquisitionCredential, {
|
|
672
1026
|
machine: host, key: spec.key, runId: input.runId, attemptId: input.attemptId,
|
|
673
1027
|
ttlMs: this.options.leaseTtlMs ?? 30 * 60_000,
|
|
674
1028
|
});
|
|
@@ -730,7 +1084,10 @@ export class WarmPodProvider {
|
|
|
730
1084
|
if (acquired.status === 409) {
|
|
731
1085
|
try {
|
|
732
1086
|
const query = new URLSearchParams({ kind: 'pod-worker-lease', machine: host, name: spec.workerId });
|
|
733
|
-
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));
|
|
734
1091
|
const row = observed.status === 200 && Array.isArray(observed.value?.resources)
|
|
735
1092
|
? observed.value.resources.find((entry: any) => entry?.name === spec.workerId) : undefined;
|
|
736
1093
|
if (row?.attrs?.state === 'quarantined' || row?.attrs?.state === 'timeout-fenced') {
|
|
@@ -790,9 +1147,16 @@ export class WarmPodProvider {
|
|
|
790
1147
|
debug.log('pod.provenance', 'lease-start', { pod: leasedCandidate.spec.name, podUid: command.podUid,
|
|
791
1148
|
leaseId: command.leaseId, runId: command.runId, attemptId: command.attemptId,
|
|
792
1149
|
requestedAt: command.requestedAt, metadata: warmLeaseMetadata(command).annotations });
|
|
793
|
-
|
|
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
|
+
}
|
|
794
1157
|
catch (error) { debug.log('pod.warm', 'late-lease-quarantine-failed', { workerId: command.workerId,
|
|
795
1158
|
leaseId: command.leaseId, generation: command.generation, error: String(error) }, { level: 'warn' }); }
|
|
1159
|
+
finally { if (quarantineTimer) clearTimeout(quarantineTimer); }
|
|
796
1160
|
return fallback('lease-unavailable');
|
|
797
1161
|
}
|
|
798
1162
|
// Acquisition is by pool key. A healthy sibling or replacement Pod must remain eligible.
|
|
@@ -963,7 +1327,7 @@ export function loadWarmPodProvider(path: string, pairingGateFileOrOptions?: str
|
|
|
963
1327
|
if (!pairingGateFile.trim()) throw new Error('warm provider pairing gate file must not be empty');
|
|
964
1328
|
options.pairingGateFile = pairingGateFile;
|
|
965
1329
|
}
|
|
966
|
-
return new WarmPodProvider({ ...options, ...runtimeOverrides });
|
|
1330
|
+
return new WarmPodProvider({ ...options, ...runtimeOverrides, rotationStatePath: options.rotationStatePath ?? `${path}.rotations` });
|
|
967
1331
|
}
|
|
968
1332
|
function currentProvider(): WarmPodProvider | null {
|
|
969
1333
|
if (configuredProvider) return configuredProvider;
|