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,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { spawn, spawnSync } from 'node:child_process';
|
|
3
|
-
import { appendFileSync, chmodSync, copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { appendFileSync, chmodSync, copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { hostname, tmpdir } from 'node:os';
|
|
5
5
|
import { createHash, randomUUID } from 'node:crypto';
|
|
6
|
-
import { basename, dirname, join, resolve } from 'node:path';
|
|
6
|
+
import { basename, dirname, isAbsolute, join, resolve } from 'node:path';
|
|
7
7
|
import { debug, redactSecretText } from '../../src/debug/log.js';
|
|
8
8
|
import { registerStandaloneLogSink } from '../../src/domains/standalone-log-sink.js';
|
|
9
9
|
import { landingFreezeMessage, readLandingFreeze } from '../../src/release-loop/landing-freeze.js';
|
|
@@ -20,7 +20,8 @@ import { planGateRadiusShadow, shadowActual, writeGateRadiusShadow, type GateRad
|
|
|
20
20
|
import { planHardRehearsalRadius, partialGateImagesMatch, preflightGateExecutionIdentity, preflightGateImageDigest } from './gate-radius-live';
|
|
21
21
|
import { actualGatePodImageDigest, gateBaselineReceipt, gateEnvFingerprint, gateGraphPins, gateSourcePins, parseGatePodRuntimeIdentity,
|
|
22
22
|
POD_SWEEP_INTEGRATION_ONLY } from './gate-baseline-receipt';
|
|
23
|
-
import { gateImagePinPath,
|
|
23
|
+
import { gateImagePinPath, normalizeGateImageDigest, pinnedGateImageSync as buildPinnedGateImageSync, readGateImagePin,
|
|
24
|
+
readGateImagePinForRun, replaceGateImagePin, resolveGateImagePin, writeGateImagePin } from './gate-image-pin.js';
|
|
24
25
|
import { gateReceiptRegistryPath, planGateTrainReceiptCarry } from './gate-train-receipts.js';
|
|
25
26
|
import { gateEnvFingerprint as gateReceiptEnvFingerprint, gateLockfileHash } from '../../src/self-implement/gate-receipt-registry.js';
|
|
26
27
|
import { SPAWN_SYNC_MAX_BUFFER } from '../../src/util/spawn-sync-output.js';
|
|
@@ -36,13 +37,15 @@ import { emitNodeResult, readGraphContext } from './node-verdict.js';
|
|
|
36
37
|
import { isGateExecutor, type GateExecutor } from './gate-executor.js';
|
|
37
38
|
import { runPodCommand, parsePodCpu, podCpuMillis, resolvePodCpu, podCommandImageFor, type RunPodCommandOptions, type PodCommandResult, type PodCpu } from '../../src/task-orchestrator/surfaces/pod-command-job.js';
|
|
38
39
|
import { POD_BUN_CACHE_HOST_PATH, parseInstallSeconds, podBunCacheVolume } from '../../src/task-orchestrator/surfaces/pod-bun-cache.js';
|
|
39
|
-
import { PodPoolScheduler, parsePodPool, checkPodPool, defaultRemoteRun, snapshotWarmPoolMembers, type PodPoolMember } from '../../src/task-orchestrator/surfaces/pod-pool.js';
|
|
40
|
+
import { NODE_REGISTRY, PodPoolScheduler, parsePodPool, checkPodPool, defaultRemoteRun, snapshotWarmPoolMembers, syncPoolImages, type PodPoolMember } from '../../src/task-orchestrator/surfaces/pod-pool.js';
|
|
40
41
|
import { POD_INSTALL_SLOTS_DEFAULT, POD_INSTALL_SLOTS_HOST_PATH, installSlotScript } from '../../src/task-orchestrator/surfaces/pod-install-slots.js';
|
|
41
42
|
import { gateDepsInstallScript, parseGateDeps } from '../../src/task-orchestrator/surfaces/pod-gate-deps.js';
|
|
42
43
|
import { warmReferenceFromJobArtifacts, writeWarmJobManifest } from './gate-warm-artifacts.js';
|
|
43
44
|
import { assessWarmWorkerShadow, parseWarmWorkerReply, type WarmWorkerCommand, type WarmWorkerFileResult, type WarmWorkerKey } from '../../src/task-orchestrator/surfaces/pod-warm-worker-protocol.js';
|
|
44
45
|
import type { WarmPodTransportResult } from '../../src/task-orchestrator/surfaces/pod-warm-worker-pod.js';
|
|
45
|
-
import { requestWarmPodReplay, finalizeWarmPodReplay, quarantineWarmPodReplay, type WarmReplayResult } from '../../src/task-orchestrator/surfaces/pod-warm-worker-provider.js';
|
|
46
|
+
import { configureWarmPodProvider, loadWarmPodProvider, requestWarmPodReplay, finalizeWarmPodReplay, quarantineWarmPodReplay, type WarmReplayInput, type WarmReplayResult } from '../../src/task-orchestrator/surfaces/pod-warm-worker-provider.js';
|
|
47
|
+
import { DEFAULT_CONTROL_PORT } from '../../src/control-plane/server.js';
|
|
48
|
+
import { createRunScopedWarmControlForward, startWarmControlPortForward } from './warm-control-forward.js';
|
|
46
49
|
import { resolveWarmPoolProfile } from '../../src/self-implement/pod-warm-pool-profile.js';
|
|
47
50
|
import { decideWarmReuse, type WarmReuseSwitchConfig, type WarmReuseEvidence } from '../../src/task-orchestrator/surfaces/pod-warm-switch.js';
|
|
48
51
|
import { getUserConfig } from '../../src/user-config.js';
|
|
@@ -52,7 +55,7 @@ import { markGateShardUnmeasured, nextGateShardAttempt, readGateShardManifest, r
|
|
|
52
55
|
import { planGateShardDispatch, summarizeGateProgress, type GateProgressGroup, type GateShardReceipt } from './gate-shard-dispatch.js';
|
|
53
56
|
import { defaultKubectl } from '../../src/task-orchestrator/surfaces/self-implement-pod.js';
|
|
54
57
|
import { executeTuiNodeCheck, type ExecutedTuiCheck } from './tui-node-checks.js';
|
|
55
|
-
import { runWithReleaseGateTraffic } from './gate-traffic-mode.js';
|
|
58
|
+
import { runWithReleaseGateTraffic, runWithReleasePwaTraffic } from './gate-traffic-mode.js';
|
|
56
59
|
import { emitPodLifecycle, gatePodLifecycleEvent, gatePodOutcome } from '../../src/task-orchestrator/surfaces/pod-lifecycle-stages.js';
|
|
57
60
|
import { gateHostReservationRoot, reserveReleaseGateHosts } from '../../src/release-loop/gate-host-reservation.js';
|
|
58
61
|
import { normRoot } from '../../src/instance/resolve.js';
|
|
@@ -111,6 +114,8 @@ function redactedGateLogTail(input: string): string {
|
|
|
111
114
|
* Every field falls back to the real tool, so an absent seam is today's behaviour byte for byte.
|
|
112
115
|
*/
|
|
113
116
|
export interface GateTools {
|
|
117
|
+
/** Graph run identity passed to pool admission for shadow observations. */
|
|
118
|
+
graphRunId?: string | null;
|
|
114
119
|
/** Wall clock the gate measures with; absent = `Date.now`. */
|
|
115
120
|
now?: () => number;
|
|
116
121
|
/** The Pod Job runner (the fake Pod stage of a scenario); absent = the positional `podCommand`, else `runPodCommand`. */
|
|
@@ -227,6 +232,10 @@ export function reconcileStaleGateShardBoard(input: {
|
|
|
227
232
|
}
|
|
228
233
|
export interface GateOptions {
|
|
229
234
|
commit: string;
|
|
235
|
+
/** Explicit pre-cut warm↔fresh comparison, persisted before the ordinary gate reads warmHistory. */
|
|
236
|
+
warmPrecutCanary?: { file: string };
|
|
237
|
+
/** RELEASE-POLICY-PACK — the gate Pod image is tied to this resolved cut commit. */
|
|
238
|
+
imagePin?: 'cut-commit';
|
|
230
239
|
/** The graph run that launched this gate; direct hard rehearsal instead pins source bytes explicitly. */
|
|
231
240
|
graphRunId?: string;
|
|
232
241
|
/** Only a direct hard rehearsal may pin graph bytes from its measured Git commit. */
|
|
@@ -250,6 +259,7 @@ export interface GateOptions {
|
|
|
250
259
|
recheckFileLimit?: number;
|
|
251
260
|
/** freshShards — GATE-SHARD-RESUME off: every shard starts a Pod even when this logDir already holds its result. */
|
|
252
261
|
pod?: { pool: string; shards?: number; shardTimeoutSeconds?: number; durationSource?: string; bunCache?: string; freshShards?: boolean;
|
|
262
|
+
imagePin?: 'cut-commit';
|
|
253
263
|
/** One immutable container image identity for this gate graph run and all its resumes. */
|
|
254
264
|
pinnedImageDigest?: string;
|
|
255
265
|
pinnedRunId?: string;
|
|
@@ -335,14 +345,64 @@ export interface GateOptions {
|
|
|
335
345
|
}
|
|
336
346
|
|
|
337
347
|
/** Gate-profile opt-in wires the producer only for the process entry, never a test runner seam. */
|
|
338
|
-
export function withWarmGateHooks(opts: GateOptions, config: unknown, injectedRunner = false
|
|
348
|
+
export function withWarmGateHooks(opts: GateOptions, config: unknown, injectedRunner = false,
|
|
349
|
+
onWarmCleanup?: (cleanup: () => Promise<void>) => void): GateOptions {
|
|
339
350
|
if (injectedRunner || !opts.pod || resolveWarmPoolProfile(config) !== 'gate') return opts;
|
|
340
351
|
const warmReuseConfig = config && typeof config === 'object' && 'pod' in config
|
|
341
352
|
&& config.pod && typeof config.pod === 'object' && 'warmReuse' in config.pod
|
|
342
353
|
? config.pod.warmReuse as WarmReuseSwitchConfig : undefined;
|
|
343
|
-
|
|
354
|
+
const lifecycle = createRunScopedWarmControlForward(async () => {
|
|
355
|
+
const configPath = process.env.ELANOUS_WARM_POD_PROVIDER_CONFIG;
|
|
356
|
+
if (!configPath) throw new Error('warm provider config unavailable');
|
|
357
|
+
const target = warmProviderTarget(configPath);
|
|
358
|
+
const forward = await startWarmControlPortForward({ context: target.context, namespace: target.namespace,
|
|
359
|
+
service: 'elanous-control', remotePort: DEFAULT_CONTROL_PORT });
|
|
360
|
+
try {
|
|
361
|
+
const provider = loadWarmPodProvider(configPath, { runtimeOverrides: { controlUrl: forward.url } });
|
|
362
|
+
configureWarmPodProvider(provider);
|
|
363
|
+
return forward;
|
|
364
|
+
} catch (error) {
|
|
365
|
+
await forward.stop();
|
|
366
|
+
throw error;
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
const replay: NonNullable<GateOptions['pod']>['warmReplay'] = async (input) => {
|
|
370
|
+
try { await lifecycle.ensure(); }
|
|
371
|
+
catch (error) {
|
|
372
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
373
|
+
debug.log('pod.warm.control-forward', 'unavailable', { reason, fallback: 'fresh-job' }, { level: 'error' });
|
|
374
|
+
return { command: null, transport: { status: 'fallback', reason: 'control-unavailable', reply: null } };
|
|
375
|
+
}
|
|
376
|
+
return requestWarmPodReplay(input);
|
|
377
|
+
};
|
|
378
|
+
const cleanup = async () => {
|
|
379
|
+
try { await lifecycle.stop(); }
|
|
380
|
+
finally { configureWarmPodProvider(null); }
|
|
381
|
+
};
|
|
382
|
+
onWarmCleanup?.(cleanup);
|
|
383
|
+
return { ...opts, pod: { ...opts.pod, warmReplay: replay,
|
|
344
384
|
warmFinalize: finalizeWarmPodReplay, warmQuarantine: quarantineWarmPodReplay, warmProducerEnabled: true, warmReuseConfig } };
|
|
345
385
|
}
|
|
386
|
+
|
|
387
|
+
function warmProviderTarget(path: string): { context: string; namespace: string } {
|
|
388
|
+
if (!isAbsolute(path) || (statSync(path).mode & 0o077) !== 0) throw new Error('warm provider config must be private');
|
|
389
|
+
const parsed: unknown = JSON.parse(readFileSync(path, 'utf8'));
|
|
390
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('invalid warm provider config');
|
|
391
|
+
const data = parsed as { candidates?: Array<{ spec?: { kubectlContext?: string; namespace?: string } }>;
|
|
392
|
+
poolTemplates?: Array<{ spec?: { kubectlContext?: string; namespace?: string } }> };
|
|
393
|
+
const rows = [...(data.candidates ?? []), ...(data.poolTemplates ?? [])];
|
|
394
|
+
const targets = new Map<string, { context: string; namespace: string }>();
|
|
395
|
+
for (const row of rows) {
|
|
396
|
+
const context = row.spec?.kubectlContext;
|
|
397
|
+
const namespace = row.spec?.namespace;
|
|
398
|
+
if (typeof context !== 'string' || !context || typeof namespace !== 'string' || !namespace)
|
|
399
|
+
throw new Error('warm provider target is incomplete');
|
|
400
|
+
targets.set(`${context}/${namespace}`, { context, namespace });
|
|
401
|
+
}
|
|
402
|
+
if (targets.size !== 1) throw new Error('warm control forward requires one cluster and namespace per gate run');
|
|
403
|
+
return [...targets.values()][0]!;
|
|
404
|
+
}
|
|
405
|
+
|
|
346
406
|
export interface GateResult {
|
|
347
407
|
outcome: 'ok' | 'regression' | 'error';
|
|
348
408
|
commit: string;
|
|
@@ -409,29 +469,151 @@ const WARM_REPLAY_BUDGET = 3;
|
|
|
409
469
|
interface WarmVerdictSample { version: string; commit: string; baselineCommit: string; warmShadow: WarmShadowObservation[] }
|
|
410
470
|
const recordValue = (value: unknown): Record<string, unknown> | null =>
|
|
411
471
|
value !== null && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : null;
|
|
472
|
+
function validWarmShadowObservation(value: unknown): value is WarmShadowObservation {
|
|
473
|
+
const row = recordValue(value);
|
|
474
|
+
if (!row || !Array.isArray(row.files) || row.files.length !== 1 || typeof row.files[0] !== 'string'
|
|
475
|
+
|| !/^(?:[\w.-]+\/)*[\w.-]+\.test\.[cm]?[jt]sx?$/.test(row.files[0])
|
|
476
|
+
|| row.files[0].split('/').some((part) => part === '.' || part === '..')
|
|
477
|
+
|| typeof row.attemptId !== 'string' || !row.attemptId || typeof row.reason !== 'string'
|
|
478
|
+
|| !['observed', 'fallback', 'unmeasured'].includes(String(row.status))
|
|
479
|
+
|| !['yes', 'no', 'failed', 'unavailable'].includes(String(row.quarantined))) return false;
|
|
480
|
+
if (row.finalized !== undefined && typeof row.finalized !== 'boolean') return false;
|
|
481
|
+
if (row.cutSha !== undefined && typeof row.cutSha !== 'string') return false;
|
|
482
|
+
if (row.baselineSha !== undefined && typeof row.baselineSha !== 'string') return false;
|
|
483
|
+
if (row.workerKey !== undefined) {
|
|
484
|
+
const key = recordValue(row.workerKey);
|
|
485
|
+
if (!key || typeof key.context !== 'string' || !key.context || typeof key.imageDigest !== 'string' || !key.imageDigest
|
|
486
|
+
|| typeof key.lockHash !== 'string' || !key.lockHash || typeof key.architecture !== 'string' || !key.architecture) return false;
|
|
487
|
+
}
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
490
|
+
function validWarmVerdictSample(value: unknown): value is WarmVerdictSample {
|
|
491
|
+
const row = recordValue(value);
|
|
492
|
+
return !!row && typeof row.version === 'string' && typeof row.commit === 'string'
|
|
493
|
+
&& typeof row.baselineCommit === 'string' && Array.isArray(row.warmShadow)
|
|
494
|
+
&& row.warmShadow.every(validWarmShadowObservation);
|
|
495
|
+
}
|
|
412
496
|
/** The verdict is atomically replaced; carry a bounded prior-verdict window in that same SSOT. */
|
|
413
497
|
function readWarmVerdictHistory(path: string): WarmVerdictSample[] | undefined {
|
|
414
498
|
if (!existsSync(path)) return [];
|
|
415
499
|
try {
|
|
416
500
|
const current = recordValue(JSON.parse(readFileSync(path, 'utf8')));
|
|
417
501
|
if (!current || !Array.isArray(current.warmHistory)) return undefined;
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
502
|
+
if (current.warmHistory.some((entry) => !validWarmVerdictSample(entry))) return undefined;
|
|
503
|
+
const hasCurrentSample = ['version', 'commit', 'baselineCommit', 'warmShadow'].some((key) => key in current);
|
|
504
|
+
const history = [...current.warmHistory];
|
|
505
|
+
if (current.warmHistoryOnly !== true || hasCurrentSample) {
|
|
506
|
+
const currentSample = { version: current.version, commit: current.commit,
|
|
507
|
+
baselineCommit: current.baselineCommit, warmShadow: current.warmShadow };
|
|
508
|
+
if (!validWarmVerdictSample(currentSample)) return undefined;
|
|
509
|
+
if (current.warmHistoryOnly !== true) history.push(currentSample);
|
|
510
|
+
}
|
|
511
|
+
return history.slice(-WARM_VERDICT_HISTORY_LIMIT) as WarmVerdictSample[];
|
|
424
512
|
} catch { return undefined; }
|
|
425
513
|
}
|
|
514
|
+
|
|
515
|
+
/** Append one actual pre-cut replay observation to the gate verdict's bounded history, preserving its gate result. */
|
|
516
|
+
function recordWarmPrecutCanarySample(path: string, sample: WarmVerdictSample): void {
|
|
517
|
+
if (!sample.version || !/^[0-9a-f]{7,40}$/i.test(sample.commit) || !/^[0-9a-f]{7,40}$/i.test(sample.baselineCommit)
|
|
518
|
+
|| !Array.isArray(sample.warmShadow) || sample.warmShadow.length !== 1 || !sample.warmShadow.every(validWarmShadowObservation))
|
|
519
|
+
throw new Error('invalid pre-cut canary sample');
|
|
520
|
+
let current: Record<string, unknown> = {};
|
|
521
|
+
if (existsSync(path)) {
|
|
522
|
+
let decoded: unknown;
|
|
523
|
+
try { decoded = JSON.parse(readFileSync(path, 'utf8')); }
|
|
524
|
+
catch (error) { throw new Error(`gate verdict JSON is malformed: ${String(error)}`); }
|
|
525
|
+
const parsed = recordValue(decoded);
|
|
526
|
+
if (!parsed) throw new Error('gate verdict is not an object');
|
|
527
|
+
current = parsed;
|
|
528
|
+
if (current.warmHistory !== undefined && !Array.isArray(current.warmHistory)) throw new Error('gate verdict warmHistory is invalid');
|
|
529
|
+
}
|
|
530
|
+
const storedHistory = Array.isArray(current.warmHistory) ? [...current.warmHistory] : [];
|
|
531
|
+
if (storedHistory.some((entry) => !validWarmVerdictSample(entry))) throw new Error('gate verdict warmHistory contains an invalid row');
|
|
532
|
+
const history = storedHistory;
|
|
533
|
+
const hasCurrentSample = ['version', 'commit', 'baselineCommit', 'warmShadow'].some((key) => key in current);
|
|
534
|
+
if (current.warmHistoryOnly === true && hasCurrentSample) {
|
|
535
|
+
const previous = { version: current.version, commit: current.commit, baselineCommit: current.baselineCommit, warmShadow: current.warmShadow };
|
|
536
|
+
if (!validWarmVerdictSample(previous)) throw new Error('gate verdict current warm sample is invalid');
|
|
537
|
+
} else if (current.warmHistoryOnly !== true) {
|
|
538
|
+
const previous = { version: current.version, commit: current.commit, baselineCommit: current.baselineCommit, warmShadow: current.warmShadow };
|
|
539
|
+
if (!validWarmVerdictSample(previous)) throw new Error('gate verdict current warm sample is invalid');
|
|
540
|
+
history.push(previous);
|
|
541
|
+
}
|
|
542
|
+
history.push(sample);
|
|
543
|
+
const body = JSON.stringify({ ...current, warmHistory: history.slice(-WARM_VERDICT_HISTORY_LIMIT), warmHistoryOnly: true }, null, 2) + '\n';
|
|
544
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
545
|
+
const temp = join(dirname(path), `.gate-verdict-${process.pid}-${randomUUID()}.tmp`);
|
|
546
|
+
try { writeFileSync(temp, body, { mode: 0o600, flag: 'wx' }); chmodSync(temp, 0o600); renameSync(temp, path); }
|
|
547
|
+
finally { if (existsSync(temp)) rmSync(temp); }
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/** Run a bounded set of real fresh-Job ↔ warm-worker comparisons before the release gate. */
|
|
551
|
+
export async function runWarmPrecutCanary(input: { opts: GateOptions; runner: GateRunner; file: string;
|
|
552
|
+
config: unknown; historyPath: string; timeoutMs?: number }): Promise<{ samples: WarmShadowObservation[]; decision: ReturnType<typeof decideWarmReuse> }> {
|
|
553
|
+
const { opts, runner } = input;
|
|
554
|
+
if (!opts.pod || !runner.recheckPod || !opts.baselineCommit || !opts.pod.warmReplay || !opts.pod.warmFinalize || !opts.pod.warmQuarantine)
|
|
555
|
+
throw new Error('pre-cut canary requires a Pod pool, baseline commit, and warm worker hooks');
|
|
556
|
+
if (!/^(?:[\w.-]+\/)*[\w.-]+\.test\.[cm]?[jt]sx?$/.test(input.file)
|
|
557
|
+
|| input.file.split('/').some((part) => part === '.' || part === '..')) throw new Error('pre-cut canary requires one repository test file');
|
|
558
|
+
if (readWarmVerdictHistory(input.historyPath) === undefined)
|
|
559
|
+
throw new Error('gate verdict contains malformed warm history or current sample; refusing canary without writing');
|
|
560
|
+
const samples = 2;
|
|
561
|
+
if (samples > WARM_REPLAY_BUDGET) throw new Error(`pre-cut canary sample count exceeds replay budget (${WARM_REPLAY_BUDGET})`);
|
|
562
|
+
const lock = await runner.localCommand('git', ['show', `${opts.commit}:bun.lock`], opts.repo ?? process.cwd());
|
|
563
|
+
if (lock.rc !== 0 || !lock.output) throw new Error('pre-cut canary cut lock is unmeasured');
|
|
564
|
+
if (!/^[0-9a-f]{40}$/i.test(opts.baselineCommit)) throw new Error('pre-cut canary baseline commit is invalid');
|
|
565
|
+
const baseline = await runner.localCommand('git', ['cat-file', '-e', `${opts.baselineCommit}^{commit}`], opts.repo ?? process.cwd());
|
|
566
|
+
if (baseline.rc !== 0) throw new Error(`pre-cut canary baseline commit is absent: ${opts.baselineCommit.slice(0, 12)}`);
|
|
567
|
+
const trackedFile = await runner.localCommand('git', ['cat-file', '-e', `${opts.commit}:${input.file}`], opts.repo ?? process.cwd());
|
|
568
|
+
if (trackedFile.rc !== 0) throw new Error(`pre-cut canary test file is absent from cut ${opts.commit.slice(0, 12)}`);
|
|
569
|
+
const lockHash = createHash('sha256').update(lock.output).digest('hex');
|
|
570
|
+
const pod = { ...opts.pod, warmProducerEnabled: false, warmCutSha: opts.commit, warmBaselineSha: opts.baselineCommit,
|
|
571
|
+
warmEligible: true, warmReplayOwner: true, warmShadowScope: { requested: false, attempts: 0 } };
|
|
572
|
+
const observations: WarmShadowObservation[] = [];
|
|
573
|
+
const attemptIds = new Set<string>();
|
|
574
|
+
for (let index = 0; index < samples; index++) {
|
|
575
|
+
let observation: WarmShadowObservation;
|
|
576
|
+
let failure: Error | undefined;
|
|
577
|
+
try {
|
|
578
|
+
const result = await runner.recheckPod(input.file, opts.commit, pod, input.timeoutMs ?? GATE_ISOLATED_TIMEOUT_MS);
|
|
579
|
+
observation = result.warmShadow ?? { files: [input.file], attemptId: `precut-unmeasured-${index + 1}-${randomUUID()}`,
|
|
580
|
+
status: 'unmeasured', reason: result.rc === 0 ? 'warm-shadow-missing' : `fresh-job-rc-${result.rc}`, quarantined: 'no' };
|
|
581
|
+
if (result.rc !== 0) { observation = { ...observation, status: 'unmeasured', reason: `fresh-job-rc-${result.rc}` };
|
|
582
|
+
failure = new Error(`pre-cut fresh Job failed on attempt ${index + 1}: rc=${result.rc}`); }
|
|
583
|
+
else if (observation.status !== 'observed' || !observation.workerKey
|
|
584
|
+
|| observation.files.length !== 1 || observation.files[0] !== input.file
|
|
585
|
+
|| observation.cutSha !== opts.commit || observation.baselineSha !== opts.baselineCommit
|
|
586
|
+
|| observation.workerKey.lockHash !== lockHash || !['match', 'result'].includes(observation.reason)
|
|
587
|
+
|| attemptIds.has(observation.attemptId)
|
|
588
|
+
|| (observation.reason === 'match' && (observation.finalized !== true || observation.quarantined !== 'no'))) {
|
|
589
|
+
failure = new Error(`pre-cut warm comparison was not measured on attempt ${index + 1}: ${observation.reason}`);
|
|
590
|
+
}
|
|
591
|
+
} catch (error) {
|
|
592
|
+
observation = { files: [input.file], attemptId: `precut-error-${index + 1}-${randomUUID()}`,
|
|
593
|
+
status: 'unmeasured', reason: String(error), quarantined: 'unavailable' };
|
|
594
|
+
failure = error instanceof Error ? error : new Error(String(error));
|
|
595
|
+
}
|
|
596
|
+
attemptIds.add(observation.attemptId);
|
|
597
|
+
observations.push(observation);
|
|
598
|
+
recordWarmPrecutCanarySample(input.historyPath, { version: opts.version, commit: opts.commit,
|
|
599
|
+
baselineCommit: opts.baselineCommit, warmShadow: [observation] });
|
|
600
|
+
if (failure) throw failure;
|
|
601
|
+
}
|
|
602
|
+
const history = readWarmVerdictHistory(input.historyPath);
|
|
603
|
+
const evidence = warmEvidenceFromVerdicts(history, opts.version, opts.commit, opts.baselineCommit, lockHash);
|
|
604
|
+
const warmReuseConfig = (input.config && typeof input.config === 'object' && 'pod' in input.config
|
|
605
|
+
&& recordValue((input.config as Record<string, unknown>).pod)?.warmReuse) as WarmReuseSwitchConfig | undefined;
|
|
606
|
+
return { samples: observations, decision: decideWarmReuse('gate-recheck', warmReuseConfig,
|
|
607
|
+
evidence ? { 'gate-recheck': evidence } : undefined) };
|
|
608
|
+
}
|
|
426
609
|
/** Every counted match has a measured, finalized worker with this exact cut, baseline and lock. */
|
|
427
610
|
export function warmEvidenceFromVerdicts(history: readonly WarmVerdictSample[] | undefined,
|
|
428
611
|
version: string, head: string, baseline: string, lock: string): WarmReuseEvidence | undefined {
|
|
429
612
|
if (!history?.length || !lock) return undefined;
|
|
430
|
-
const
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
});
|
|
613
|
+
const matchingHistory = history.filter((verdict) => verdict.version === version && verdict.commit === head
|
|
614
|
+
&& verdict.baselineCommit === baseline);
|
|
615
|
+
const rows = matchingHistory.flatMap((verdict) => verdict.warmShadow.length ? verdict.warmShadow : [null]);
|
|
616
|
+
if (!rows.length) return undefined;
|
|
435
617
|
const attempts = new Set<string>();
|
|
436
618
|
const key = rows.at(-1)?.workerKey;
|
|
437
619
|
if (rows.some((entry) => {
|
|
@@ -1575,7 +1757,45 @@ export interface GateAdmission {
|
|
|
1575
1757
|
acquire(signal: AbortSignal): Promise<() => void>;
|
|
1576
1758
|
/** The pool-wide reading admission waits on (recommended · limitedBy · memorySlots); null before any reading. */
|
|
1577
1759
|
waitReason(): string | null;
|
|
1760
|
+
/** Optional deterministic-test observation override; production uses poolGateAdmission's unknown-only provider. */
|
|
1761
|
+
priorityShadowObservation?(gateWaiting: boolean): GatePriorityShadowObservation;
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
export type GatePriorityImageState = 'verified' | 'mismatch' | 'unknown';
|
|
1765
|
+
export interface GatePriorityShadowObservation {
|
|
1766
|
+
gateRunId: string | null;
|
|
1767
|
+
host: string | null;
|
|
1768
|
+
competingRunId: string | null;
|
|
1769
|
+
competingHost: string | null;
|
|
1770
|
+
gateImageState: GatePriorityImageState;
|
|
1771
|
+
competingImageState: GatePriorityImageState;
|
|
1772
|
+
gateWaiting: boolean;
|
|
1773
|
+
lowerPriorityWaiting: boolean | null;
|
|
1774
|
+
}
|
|
1775
|
+
export interface GatePriorityShadowDecision extends GatePriorityShadowObservation {
|
|
1776
|
+
event: 'gate-priority-shadow-decision';
|
|
1777
|
+
decision: 'gate-first' | 'no-contention' | 'insufficient-evidence';
|
|
1778
|
+
selectionRationale: string;
|
|
1578
1779
|
}
|
|
1780
|
+
|
|
1781
|
+
/** A shadow-only policy decision; it has no pool or Pod handle and cannot change runtime state. */
|
|
1782
|
+
export function gatePriorityShadowDecision(input: GatePriorityShadowObservation): GatePriorityShadowDecision {
|
|
1783
|
+
const base = { event: 'gate-priority-shadow-decision' as const, ...input };
|
|
1784
|
+
if (!input.gateWaiting || input.lowerPriorityWaiting === false) {
|
|
1785
|
+
return { ...base, decision: 'no-contention', selectionRationale: !input.gateWaiting ? 'gate-not-waiting' : 'no-lower-priority-waiter-observed' };
|
|
1786
|
+
}
|
|
1787
|
+
if (input.lowerPriorityWaiting !== true) {
|
|
1788
|
+
return { ...base, decision: 'insufficient-evidence', selectionRationale: 'lower-priority-wait-state-unknown' };
|
|
1789
|
+
}
|
|
1790
|
+
if (!input.gateRunId || !input.competingRunId || !input.host || input.host !== input.competingHost) {
|
|
1791
|
+
return { ...base, decision: 'insufficient-evidence', selectionRationale: 'same-host-run-identities-unavailable' };
|
|
1792
|
+
}
|
|
1793
|
+
if (input.gateImageState !== 'verified' || input.competingImageState !== 'verified') {
|
|
1794
|
+
return { ...base, decision: 'insufficient-evidence', selectionRationale: 'image-state-not-verified' };
|
|
1795
|
+
}
|
|
1796
|
+
return { ...base, decision: 'gate-first', selectionRationale: 'same-host-contention-and-verified-gate-image; gate-policy-priority' };
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1579
1799
|
export const GATE_ADMISSION_WAIT_SECONDS_DEFAULT = 1800;
|
|
1580
1800
|
/** Queue pressure scales the default wait but never creates an unbounded admission timer. */
|
|
1581
1801
|
export function gateAdmissionWaitSeconds(shards: number, slots: number): number {
|
|
@@ -1625,17 +1845,38 @@ export function defaultGateMemberProbe(member: PodPoolMember, tools: GateTools =
|
|
|
1625
1845
|
return !member.sshHost || (tools.remoteRun ?? defaultRemoteRun)(member.sshHost, 'true').status === 0;
|
|
1626
1846
|
}
|
|
1627
1847
|
|
|
1628
|
-
export function poolGateAdmission(pool: PodPoolScheduler
|
|
1629
|
-
|
|
1848
|
+
export function poolGateAdmission(pool: PodPoolScheduler, gateRunId: string | null = null,
|
|
1849
|
+
priorityShadowObservation?: (gateWaiting: boolean) => GatePriorityShadowObservation): GateAdmission {
|
|
1850
|
+
const singleHost = pool.members.length === 1 ? (pool.members[0]!.sshHost ?? hostname()) : null;
|
|
1851
|
+
// The optional provider is a deterministic test seam only. Production never fabricates a competitor or image fact.
|
|
1852
|
+
return {
|
|
1853
|
+
acquire: (signal) => pool.acquireAdmission(signal, undefined, { gate: true }),
|
|
1854
|
+
waitReason: () => pool.waitReason(),
|
|
1855
|
+
// At admission time a multi-host pool has not selected a host yet. Existing measurements do not expose
|
|
1856
|
+
// competing run ids or image states, so keep those unknown rather than deriving them from Job names.
|
|
1857
|
+
priorityShadowObservation: priorityShadowObservation ?? ((gateWaiting) => ({ gateRunId, host: singleHost, competingRunId: null, competingHost: null,
|
|
1858
|
+
gateImageState: 'unknown', competingImageState: 'unknown', gateWaiting, lowerPriorityWaiting: null })),
|
|
1859
|
+
};
|
|
1630
1860
|
}
|
|
1631
|
-
export async function admitGateShard(admission: GateAdmission | undefined, o: { waitMs: number; onWait: (reason: string) => void; reasonPollMs?: number; firstReasonMs?: number
|
|
1861
|
+
export async function admitGateShard(admission: GateAdmission | undefined, o: { waitMs: number; onWait: (reason: string) => void; reasonPollMs?: number; firstReasonMs?: number;
|
|
1862
|
+
onPriorityShadow?: (decision: GatePriorityShadowDecision) => void }): Promise<GateAdmissionResult> {
|
|
1632
1863
|
const noop = () => {};
|
|
1633
1864
|
if (!admission || !(o.waitMs > 0)) return { outcome: 'off', waitedMs: 0, release: noop };
|
|
1634
1865
|
const started = Date.now();
|
|
1635
1866
|
const controller = new AbortController();
|
|
1636
1867
|
let timedOut = false;
|
|
1637
1868
|
let lastReason: string | undefined;
|
|
1638
|
-
|
|
1869
|
+
let priorityShadowLogged = false;
|
|
1870
|
+
const tell = () => {
|
|
1871
|
+
lastReason = gateAdmissionWaitText(admission.waitReason());
|
|
1872
|
+
if (!priorityShadowLogged && admission.priorityShadowObservation) {
|
|
1873
|
+
priorityShadowLogged = true;
|
|
1874
|
+
const decision = gatePriorityShadowDecision(admission.priorityShadowObservation(true));
|
|
1875
|
+
if (o.onPriorityShadow) o.onPriorityShadow(decision);
|
|
1876
|
+
else debug.log('release-loop.gate', 'gate-priority-shadow-decision', decision);
|
|
1877
|
+
}
|
|
1878
|
+
o.onWait(lastReason);
|
|
1879
|
+
};
|
|
1639
1880
|
const deadline = setTimeout(() => { timedOut = true; controller.abort(); }, o.waitMs);
|
|
1640
1881
|
// A grant inside the first moment never touches the shard row (no flapping in shards.json).
|
|
1641
1882
|
let ticker: ReturnType<typeof setInterval> | undefined;
|
|
@@ -1678,6 +1919,27 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1678
1919
|
const ssh = tools.ssh ?? defaultSshCommand;
|
|
1679
1920
|
const remoteRun = tools.remoteRun ?? defaultRemoteRun;
|
|
1680
1921
|
const runPod = tools.podCommand ?? podCommand;
|
|
1922
|
+
const pinnedGateImageSync = (pod: NonNullable<GateOptions['pod']>, commit: string): RunPodCommandOptions['syncImages'] | undefined => {
|
|
1923
|
+
const hasDigest = Boolean(pod.pinnedImageDigest?.trim());
|
|
1924
|
+
const hasRunId = Boolean(pod.pinnedRunId?.trim());
|
|
1925
|
+
if (hasDigest !== hasRunId) throw new Error('gate pinned image: both pinnedImageDigest and pinnedRunId are required');
|
|
1926
|
+
if (!hasDigest) return undefined;
|
|
1927
|
+
return async (members, image, localCommit) => {
|
|
1928
|
+
if (localCommit !== commit) throw new Error('gate pinned image: requested source commit changed');
|
|
1929
|
+
const probe = preflightGateImageDigest;
|
|
1930
|
+
const poolSpecFor = (member: PodPoolMember) => `${member.context}${member.sshHost ? `@${member.sshHost}` : ''}:${member.capacity}#${member.registry ?? NODE_REGISTRY}`;
|
|
1931
|
+
const result = buildPinnedGateImageSync({ runId: pod.pinnedRunId!, commit, digest: pod.pinnedImageDigest!, image,
|
|
1932
|
+
members: members.map((member) => ({ context: member.context, registry: member.registry ?? NODE_REGISTRY })),
|
|
1933
|
+
probeTagDigest: (member) => {
|
|
1934
|
+
const candidate = members.find((row) => row.context === member.context && (row.registry ?? NODE_REGISTRY) === member.registry);
|
|
1935
|
+
return candidate ? probe(poolSpecFor(candidate), commit) : null;
|
|
1936
|
+
} });
|
|
1937
|
+
for (const drift of result.tagDrifts) debug.log('release-loop.gate', 'image-tag-drift', {
|
|
1938
|
+
...drift, action: 'skip-push-use-pinned-digest',
|
|
1939
|
+
}, { level: 'warn' });
|
|
1940
|
+
return result.images;
|
|
1941
|
+
};
|
|
1942
|
+
};
|
|
1681
1943
|
// GATE-HOSTMIRROR-EXPLICIT: resolved once per runner and handed to every podCommand (shard · retry · recheck).
|
|
1682
1944
|
let gateMirror: GateHostMirror | undefined;
|
|
1683
1945
|
const gateHostMirror = (pod: NonNullable<GateOptions['pod']>): { hostMirror?: string } => {
|
|
@@ -1720,6 +1982,9 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1720
1982
|
if (!pod.pool || !Number.isSafeInteger(shardCount) || shardCount < 1
|
|
1721
1983
|
|| !Number.isSafeInteger(deadlineSeconds) || deadlineSeconds < 1
|
|
1722
1984
|
|| !Number.isSafeInteger(isolationConcurrency) || isolationConcurrency < 1) throw new Error('invalid pod sweep options');
|
|
1985
|
+
const hasPinnedDigest = Boolean(pod.pinnedImageDigest?.trim());
|
|
1986
|
+
const hasPinnedRunId = Boolean(pod.pinnedRunId?.trim());
|
|
1987
|
+
if (hasPinnedDigest !== hasPinnedRunId) throw new Error('gate pinned image: both pinnedImageDigest and pinnedRunId are required');
|
|
1723
1988
|
const head = await command('git', ['rev-parse', 'HEAD'], tree);
|
|
1724
1989
|
check(head, 'cut tree HEAD');
|
|
1725
1990
|
const commit = head.output.trim();
|
|
@@ -1848,7 +2113,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1848
2113
|
const admissionWaitSeconds = pod.admissionWaitSeconds ?? gateAdmissionWaitSeconds(shards.length, rootSlots);
|
|
1849
2114
|
if (!Number.isSafeInteger(admissionWaitSeconds) || admissionWaitSeconds < 0) throw new Error('invalid pod sweep options');
|
|
1850
2115
|
// An injected pool (tests) runs without admission unless `pod.admission` is passed; the gate's own pool asks it.
|
|
1851
|
-
const gateAdmission = pod.admission ?? tools.admission ?? (poolOverride ? undefined : poolGateAdmission(poolScheduler));
|
|
2116
|
+
const gateAdmission = pod.admission ?? tools.admission ?? (poolOverride ? undefined : poolGateAdmission(poolScheduler, tools.graphRunId ?? null));
|
|
1852
2117
|
debug.log('release-loop.gate', 'pod-admission', { waitSeconds: admissionWaitSeconds, source: pod.admissionWaitSeconds !== undefined ? 'config' : 'default', enabled: !!gateAdmission && admissionWaitSeconds > 0,
|
|
1853
2118
|
rootSlots, shards: shards.length });
|
|
1854
2119
|
// GATE-POD-STALL-FAILFAST: a shard with no test output within the stall window is stopped and recorded unmeasured.
|
|
@@ -2227,7 +2492,9 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2227
2492
|
pool: pod.pool, poolScheduler: trackedPool(shard, branch, jobName, resultFile, attemptId, (member) => {
|
|
2228
2493
|
stamp('poolPlaced'); jobContext = member.context; jobContextForCapture = member.context; placedOn = member.context; placed();
|
|
2229
2494
|
if (branch === '-tail') tailPlacedHost = member.context;
|
|
2230
|
-
}, skipMembers, depth === 0 && branch === '' && hostRetries === 0 && !retriedNoOutput && !batchIsolation && skipMembers.size === 0), clone: true, source: { kind: 'commit', sha: commit },
|
|
2495
|
+
}, skipMembers, depth === 0 && branch === '' && hostRetries === 0 && !retriedNoOutput && !batchIsolation && skipMembers.size === 0), clone: true, source: { kind: 'commit', sha: commit },
|
|
2496
|
+
...(pod.imagePin === 'cut-commit' || pod.pinnedImageDigest || pod.pinnedRunId ? { imageCommit: commit } : {}),
|
|
2497
|
+
...(pod.pinnedImageDigest || pod.pinnedRunId ? { syncImages: pinnedGateImageSync(pod, commit)! } : {}), deadlineSeconds: shardDeadline,
|
|
2231
2498
|
...gateHostMirror(pod),
|
|
2232
2499
|
...(multiHost ? { statusUnreadableMs: memberUnreadableSeconds * 1000 } : {}),
|
|
2233
2500
|
...(bunCache ? { bunCache } : {}),
|
|
@@ -2854,7 +3121,9 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2854
3121
|
let producer: RunPodCommandOptions['warmProducer'];
|
|
2855
3122
|
if (leased && replay?.transport.status === 'observed' && replay.transport.reason === 'complete') {
|
|
2856
3123
|
if (warmCommandMatchesRequest(leased, warmRequest!) && leased.podName && leased.podUid
|
|
2857
|
-
&& leased.podContext && leased.image?.endsWith(`@${leased.key.imageDigest}`)
|
|
3124
|
+
&& leased.podContext && leased.image?.endsWith(`@${leased.key.imageDigest}`)
|
|
3125
|
+
&& (!pod.pinnedImageDigest || (normalizeGateImageDigest(pod.pinnedImageDigest) !== null
|
|
3126
|
+
&& normalizeGateImageDigest(leased.key.imageDigest) === normalizeGateImageDigest(pod.pinnedImageDigest)))) {
|
|
2858
3127
|
producer = { pod: leased.podName, podUid: leased.podUid, context: leased.podContext, image: leased.image,
|
|
2859
3128
|
commit, files: ['package.json', 'bun.lock'] };
|
|
2860
3129
|
} else await quarantine('gate-producer-identity');
|
|
@@ -2868,7 +3137,9 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2868
3137
|
run: process.env.ELANOUS_RUN_ID || attemptId, phase: 'release-gate', requestedAt: new Date().toISOString(),
|
|
2869
3138
|
graphNode: 'gate-triage', commit, reason: `release gate recheck ${files.length} files`, pool: pod.pool,
|
|
2870
3139
|
memReq: POD_ISOLATION_MEMORY_LIMIT, warmReused: !!producer },
|
|
2871
|
-
source: { kind: 'commit', sha: commit }, deadlineSeconds, ...(producer ? { warmProducer: producer, image: producer.image
|
|
3140
|
+
source: { kind: 'commit', sha: commit }, deadlineSeconds, ...(producer ? { warmProducer: producer, image: producer.image } : {}),
|
|
3141
|
+
...(producer || pod.pinnedImageDigest || pod.pinnedRunId ? { imageCommit: commit } : {}),
|
|
3142
|
+
...(pod.pinnedImageDigest || pod.pinnedRunId ? { syncImages: pinnedGateImageSync(pod, commit)! } : {}),
|
|
2872
3143
|
...(bunCache ? { bunCache } : {}), ...gateHostMirror(pod),
|
|
2873
3144
|
...(installSlots > 0 ? { installSlots: POD_INSTALL_SLOTS_HOST_PATH } : {}), ...(pod.cpu ? { cpu: pod.cpu } : {}),
|
|
2874
3145
|
memoryLimit: POD_ISOLATION_MEMORY_LIMIT, name: `gate-recheck-${randomUUID()}`, command: ['bash', '-lc', shell] });
|
|
@@ -3221,9 +3492,10 @@ export function saveBaselineFileCache(ledger: string, commit: string, rows: Base
|
|
|
3221
3492
|
} finally { if (existsSync(temp)) rmSync(temp); }
|
|
3222
3493
|
}
|
|
3223
3494
|
|
|
3224
|
-
export async function judgeGate(opts: GateOptions, runner: GateRunner = createGateRunner(opts.repo ?? process.cwd(), opts.remote),
|
|
3495
|
+
export async function judgeGate(opts: GateOptions, runner: GateRunner = createGateRunner(opts.repo ?? process.cwd(), opts.remote, undefined, undefined, undefined, undefined, { graphRunId: opts.graphRunId ?? null }),
|
|
3225
3496
|
imageProbe: typeof preflightGateImageDigest = preflightGateImageDigest,
|
|
3226
|
-
identityProbe: typeof preflightGateExecutionIdentity = preflightGateExecutionIdentity
|
|
3497
|
+
identityProbe: typeof preflightGateExecutionIdentity = preflightGateExecutionIdentity,
|
|
3498
|
+
syncImages: typeof syncPoolImages = syncPoolImages): Promise<GateResult> {
|
|
3227
3499
|
if (opts.pod?.warmReplay) opts = { ...opts, pod: { ...opts.pod,
|
|
3228
3500
|
warmShadowScope: { requested: false, attempts: 0 } } };
|
|
3229
3501
|
const explicitConfig = getElanousConfigDirOverride();
|
|
@@ -3266,11 +3538,11 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
3266
3538
|
let cutSweepRc: number | null = null;
|
|
3267
3539
|
let radiusShadow: GateRadiusShadowPlan | undefined;
|
|
3268
3540
|
const trees: string[] = [];
|
|
3541
|
+
const baselineVersion = opts.baselineVersion;
|
|
3269
3542
|
try {
|
|
3270
3543
|
if (!sha.test(opts.commit) || !versionPattern.test(opts.version)
|
|
3271
3544
|
|| (opts.baselineVersion && !versionPattern.test(opts.baselineVersion))
|
|
3272
3545
|
|| (opts.baselineCommit && !sha.test(opts.baselineCommit))) throw new Error('invalid commit or version');
|
|
3273
|
-
const baselineVersion = opts.baselineVersion;
|
|
3274
3546
|
if (!baselineVersion) throw new Error('previous release version required (--baseline-version)');
|
|
3275
3547
|
if (opts.rehearsalBaseline && (opts.graphRunId || !opts.pod || !/-rc\.\d+$/.test(opts.version)))
|
|
3276
3548
|
throw new Error('rehearsal baseline requires a direct Pod gate with an isolated rc version');
|
|
@@ -3302,11 +3574,22 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
3302
3574
|
const pinPath = gateImagePinPath(root, opts.version, pinRunId);
|
|
3303
3575
|
const gateImageTag = `${podCommandImageFor([], true).split(':')[0]}:${opts.commit.slice(0, 12)}`;
|
|
3304
3576
|
let savedPin: ReturnType<typeof readGateImagePin>;
|
|
3577
|
+
let supersededPin: ReturnType<typeof readGateImagePinForRun> = null;
|
|
3578
|
+
let stalePinNeedsSuperseding = false;
|
|
3305
3579
|
try { savedPin = existsSync(pinPath) ? readGateImagePin(pinPath, pinRunId, gateImageTag, opts.commit) : null; }
|
|
3306
3580
|
catch (error) {
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3581
|
+
let prior: ReturnType<typeof readGateImagePinForRun> = null;
|
|
3582
|
+
try { prior = readGateImagePinForRun(pinPath, pinRunId, gateImageTag); } catch { /* Keep invalid or corrupt pins fail-closed. */ }
|
|
3583
|
+
if (!prior || prior.commit === opts.commit.toLowerCase()) {
|
|
3584
|
+
debug.log('release-loop.gate', 'image-pin', { runId: pinRunId, currentImages, decision: 'fail-closed',
|
|
3585
|
+
reason: 'saved-image-pin-invalid', error: String(error).slice(0, 300) }, { level: 'error' });
|
|
3586
|
+
throw new Error('gate resume refused: saved image digest pin is invalid');
|
|
3587
|
+
}
|
|
3588
|
+
supersededPin = prior;
|
|
3589
|
+
stalePinNeedsSuperseding = true;
|
|
3590
|
+
savedPin = null;
|
|
3591
|
+
debug.log('release-loop.gate', 'image-pin', { runId: pinRunId, currentImages, decision: 'stale-tip-detected',
|
|
3592
|
+
priorCommit: supersededPin.commit, nextCommit: opts.commit.toLowerCase() });
|
|
3310
3593
|
}
|
|
3311
3594
|
const savedDigest = savedPin?.digest ?? null;
|
|
3312
3595
|
let previouslyExecuted = false;
|
|
@@ -3321,11 +3604,34 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
3321
3604
|
} catch { previouslyExecuted = true; }
|
|
3322
3605
|
}
|
|
3323
3606
|
}
|
|
3324
|
-
if (!savedDigest && previouslyExecuted) {
|
|
3607
|
+
if (!savedDigest && previouslyExecuted && !supersededPin) {
|
|
3325
3608
|
debug.log('release-loop.gate', 'image-pin', { runId: opts.graphRunId, savedDigest: null, currentImages,
|
|
3326
3609
|
decision: 'fail-closed', reason: 'resume-pin-missing' }, { level: 'error' });
|
|
3327
3610
|
throw new Error('gate resume refused: saved image digest pin is missing');
|
|
3328
3611
|
}
|
|
3612
|
+
const hasPoolImage = [...poolImages.values()].some((images) => images.some((image) => normalizeGateImageDigest(image) !== null));
|
|
3613
|
+
if (!savedDigest && !hasPoolImage) {
|
|
3614
|
+
let syncResults: Awaited<ReturnType<typeof syncPoolImages>>;
|
|
3615
|
+
try { syncResults = await syncImages(members, podCommandImageFor([], true), opts.commit); }
|
|
3616
|
+
catch (error) {
|
|
3617
|
+
debug.log('release-loop.gate', 'image-pin', { runId: pinRunId, decision: 'fail-closed',
|
|
3618
|
+
reason: 'initial-image-sync-failed', error: String(error).slice(0, 300) }, { level: 'error' });
|
|
3619
|
+
throw new Error('gate image pin refused: initial image sync failed');
|
|
3620
|
+
}
|
|
3621
|
+
const failedSyncs = memberSpecs.filter((member) => !syncResults.get(member.context)?.ok);
|
|
3622
|
+
if (failedSyncs.length) {
|
|
3623
|
+
debug.log('release-loop.gate', 'image-pin', { runId: pinRunId, decision: 'fail-closed', reason: 'initial-image-sync-failed',
|
|
3624
|
+
failed: failedSyncs.map((member) => ({ context: member.context, result: syncResults.get(member.context) ?? null })) }, { level: 'error' });
|
|
3625
|
+
throw new Error('gate image pin refused: initial image sync failed');
|
|
3626
|
+
}
|
|
3627
|
+
for (const member of memberSpecs) {
|
|
3628
|
+
const digest = imageProbe(member.spec, opts.commit);
|
|
3629
|
+
currentImages[member.context] = digest;
|
|
3630
|
+
poolImages.set(member.context, digest ? [digest] : []);
|
|
3631
|
+
}
|
|
3632
|
+
debug.log('release-loop.gate', 'image-pin', { runId: pinRunId, decision: 'initial-image-sync',
|
|
3633
|
+
outcomes: [...syncResults].map(([context, result]) => ({ context, ok: result.ok, action: result.action })), currentImages });
|
|
3634
|
+
}
|
|
3329
3635
|
const resolution = resolveGateImagePin({ ...(savedDigest ? { savedDigest } : {}),
|
|
3330
3636
|
...(!savedDigest ? { initialDigest: [...poolImages.values()].flat().find(Boolean) ?? null } : {}), poolImages });
|
|
3331
3637
|
if (!resolution.ok) {
|
|
@@ -3336,7 +3642,11 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
3336
3642
|
const selected = memberSpecs.filter((member) => resolution.eligiblePools.includes(member.context));
|
|
3337
3643
|
if (!selected.length) throw new Error('gate image pin refused: no eligible pool');
|
|
3338
3644
|
if (!savedDigest) {
|
|
3339
|
-
try {
|
|
3645
|
+
try {
|
|
3646
|
+
if (stalePinNeedsSuperseding) {
|
|
3647
|
+
replaceGateImagePin(pinPath, pinRunId, resolution.digest, gateImageTag, opts.commit, gateImageTag, supersededPin!.commit);
|
|
3648
|
+
} else writeGateImagePin(pinPath, pinRunId, resolution.digest, gateImageTag, opts.commit);
|
|
3649
|
+
}
|
|
3340
3650
|
catch (error) {
|
|
3341
3651
|
debug.log('release-loop.gate', 'image-pin', { runId: pinRunId, pinnedDigest: resolution.digest,
|
|
3342
3652
|
currentImages, decision: 'fail-closed', reason: 'pin-write-failed', error: String(error).slice(0, 300) }, { level: 'error' });
|
|
@@ -3506,7 +3816,7 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
3506
3816
|
try {
|
|
3507
3817
|
const sweepResult = await runner.sweep(cutTree,
|
|
3508
3818
|
join(root, 'release', opts.version, 'gate-logs', selected ? 'cut-partial' : 'cut'),
|
|
3509
|
-
opts.pod ? { ...opts.pod, durationSource, ...(knownFailures.length ? { knownFailures } : {}), onSelectionTimed: ({ ms }) => { result.selectionMs = ms; },
|
|
3819
|
+
opts.pod ? { ...opts.pod, ...(opts.imagePin ? { imagePin: opts.imagePin } : {}), durationSource, ...(knownFailures.length ? { knownFailures } : {}), onSelectionTimed: ({ ms }) => { result.selectionMs = ms; },
|
|
3510
3820
|
// GATE-RISK-LANE — the watch list is read from this gate's own ledgers (the instance root ⊕ the machine ledger);
|
|
3511
3821
|
// the cut sweep puts those files first, the baseline sweep keeps its plan.
|
|
3512
3822
|
riskWatch: { instanceRoots: roots, ledgerRoots: roots },
|
|
@@ -3608,7 +3918,7 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
3608
3918
|
check(await runner.command('bun', ['install'], dir), `bun install ${dir}`);
|
|
3609
3919
|
const rechecked = await runner.sweep(recheckTree,
|
|
3610
3920
|
join(root, 'release', opts.version, 'gate-logs', 'baseline-attribution-recheck'),
|
|
3611
|
-
opts.pod ? { ...opts.pod, durationSource } : undefined,
|
|
3921
|
+
opts.pod ? { ...opts.pod, ...(opts.imagePin ? { imagePin: opts.imagePin } : {}), durationSource } : undefined,
|
|
3612
3922
|
targetFiles);
|
|
3613
3923
|
if (rechecked.timedOut || rechecked.abortedShards || rechecked.stalledEnv?.length) {
|
|
3614
3924
|
attribution.baselineResult = 'timeout';
|
|
@@ -3723,7 +4033,7 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
3723
4033
|
// K9b — the baseline is swept on the same Pod pool as the cut, not on this host (0.2.6: an hour of local baseline
|
|
3724
4034
|
// after a 23-minute Pod cut).
|
|
3725
4035
|
const baseRun = await runner.sweep(baseTree!, join(root, 'release', opts.version, 'gate-logs', 'baseline'),
|
|
3726
|
-
opts.pod ? { ...opts.pod, durationSource } : undefined,
|
|
4036
|
+
opts.pod ? { ...opts.pod, ...(opts.imagePin ? { imagePin: opts.imagePin } : {}), durationSource } : undefined,
|
|
3727
4037
|
present);
|
|
3728
4038
|
baseline = await localStalled(baseRun, baseTree!, baseSha, 'baseline');
|
|
3729
4039
|
} else baseline = { failures: [], errors: [] };
|
|
@@ -4174,6 +4484,14 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
|
|
|
4174
4484
|
const fromGraph: Record<string, unknown> = context?.input ?? {};
|
|
4175
4485
|
const graphCommit = context?.outputs['version-release']?.commit;
|
|
4176
4486
|
if (typeof graphCommit === 'string') fromGraph.commit = graphCommit;
|
|
4487
|
+
if (fromGraph.gateImagePin !== undefined && fromGraph.gateImagePin !== 'cut-commit') {
|
|
4488
|
+
throw new Error(`unsupported gate image pin: ${String(fromGraph.gateImagePin)}`);
|
|
4489
|
+
}
|
|
4490
|
+
// Policy-selected Pod gates must pin their container to the resolved cut commit. Host sweeps do
|
|
4491
|
+
// not consume a container image pin, and the legacy no-executor path retains its old behavior.
|
|
4492
|
+
if (fromGraph.gateExecutor === 'pod' && fromGraph.gateImagePin !== 'cut-commit') {
|
|
4493
|
+
throw new Error('gateImagePin cut-commit is required for a policy-selected release gate');
|
|
4494
|
+
}
|
|
4177
4495
|
const baselineCommit = context?.outputs.cutoff?.baseline as { sha?: unknown } | undefined;
|
|
4178
4496
|
if (typeof baselineCommit?.sha === 'string') fromGraph.previousCommit = baselineCommit.sha;
|
|
4179
4497
|
if (fromGraph.gateExecutor !== undefined && !isGateExecutor(fromGraph.gateExecutor)) throw new Error(`gateExecutor must be pod or host: ${String(fromGraph.gateExecutor)}`);
|
|
@@ -4186,6 +4504,7 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
|
|
|
4186
4504
|
|| !['darwin', 'linux'].includes((value as { os?: string }).os ?? ''))))
|
|
4187
4505
|
throw new Error('gateShardCapabilities must map pod-N to {os: darwin|linux}');
|
|
4188
4506
|
const opts: GateOptions = {
|
|
4507
|
+
...(fromGraph.gateImagePin === 'cut-commit' ? { imagePin: 'cut-commit' as const } : {}),
|
|
4189
4508
|
rehearsalTui: fromGraph.rehearsalTui === true,
|
|
4190
4509
|
recheckFileLimit: fromGraph.gateRecheckFileLimit !== undefined ? Number(fromGraph.gateRecheckFileLimit) : undefined,
|
|
4191
4510
|
commit: typeof fromGraph.commit === 'string' ? fromGraph.commit : '',
|
|
@@ -4214,12 +4533,19 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
|
|
|
4214
4533
|
};
|
|
4215
4534
|
if (isGateExecutor(fromGraph.gateExecutor)) opts.executor = fromGraph.gateExecutor;
|
|
4216
4535
|
let freshShards = false;
|
|
4536
|
+
let warmPrecutCanaryFile: string | undefined;
|
|
4217
4537
|
let rerouteShard: string | undefined;
|
|
4218
4538
|
let shardHost: string | undefined;
|
|
4219
4539
|
for (let i = 0; i < args.length; i++) {
|
|
4220
4540
|
const arg = args[i];
|
|
4221
4541
|
if (arg === '--help') return 'help';
|
|
4222
4542
|
if (arg === '--json') continue;
|
|
4543
|
+
if (arg === '--precut-canary-file') {
|
|
4544
|
+
const value = args[++i];
|
|
4545
|
+
if (!value || value.startsWith('--')) throw new Error('--precut-canary-file requires one test file');
|
|
4546
|
+
warmPrecutCanaryFile = value;
|
|
4547
|
+
continue;
|
|
4548
|
+
}
|
|
4223
4549
|
if (arg === '--full') { opts.full = true; continue; }
|
|
4224
4550
|
if (arg === '--rehearsal-baseline') { opts.rehearsalBaseline = true; continue; }
|
|
4225
4551
|
// Applied after the loop and only to a Pod gate — on its own it must not turn a local gate into a Pod one (review r5).
|
|
@@ -4264,6 +4590,7 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
|
|
|
4264
4590
|
else if (key === 'remoteMirror') opts.remoteMirror = value;
|
|
4265
4591
|
}
|
|
4266
4592
|
if (freshShards && opts.pod) opts.pod = { ...opts.pod, freshShards: true };
|
|
4593
|
+
if (warmPrecutCanaryFile) opts.warmPrecutCanary = { file: warmPrecutCanaryFile };
|
|
4267
4594
|
if ((rerouteShard || shardHost) && !opts.pod?.pool) throw new Error('shard reroute requires a Pod pool');
|
|
4268
4595
|
if (shardHost && !rerouteShard) throw new Error('--shard-host requires --reroute-shard');
|
|
4269
4596
|
if (opts.pod && rerouteShard) opts.pod = { ...opts.pod, rerouteShard, ...(shardHost ? { shardHost } : {}) };
|
|
@@ -4286,11 +4613,12 @@ export async function runGateNode(argv: string[], env: NodeJS.ProcessEnv, runner
|
|
|
4286
4613
|
judge: (opts: GateOptions, runner?: GateRunner, imageProbe?: typeof preflightGateImageDigest) => Promise<GateResult> = judgeGate,
|
|
4287
4614
|
imageProbe: typeof preflightGateImageDigest = preflightGateImageDigest): Promise<number> {
|
|
4288
4615
|
const start = Date.now();
|
|
4289
|
-
let result: GateResult;
|
|
4616
|
+
let result: GateResult | undefined;
|
|
4290
4617
|
let version = '';
|
|
4291
4618
|
let graphRunId: string | null = null;
|
|
4292
4619
|
let autohealStoreRoot = '';
|
|
4293
4620
|
let placement: { executor: string | null; host: string | null } | undefined;
|
|
4621
|
+
let warmControlCleanup: (() => Promise<void>) | undefined;
|
|
4294
4622
|
try {
|
|
4295
4623
|
const parsed = parseOptions(argv, env);
|
|
4296
4624
|
let config: unknown = null;
|
|
@@ -4298,13 +4626,28 @@ export async function runGateNode(argv: string[], env: NodeJS.ProcessEnv, runner
|
|
|
4298
4626
|
try { config = getUserConfig(); }
|
|
4299
4627
|
catch (error) { debug.log('release-loop.gate', 'warm-profile-unavailable', { error: String(error) }); }
|
|
4300
4628
|
}
|
|
4301
|
-
const opts = parsed === 'help' ? parsed : withWarmGateHooks(parsed, config, !!runner
|
|
4629
|
+
const opts = parsed === 'help' ? parsed : withWarmGateHooks(parsed, config, !!runner,
|
|
4630
|
+
cleanup => { warmControlCleanup = cleanup; });
|
|
4631
|
+
if (opts !== 'help' && opts.warmPrecutCanary) {
|
|
4632
|
+
const canaryRunner = runner ?? createGateRunner(opts.repo ?? process.cwd(), opts.remote, undefined, undefined, undefined, undefined, { graphRunId: opts.graphRunId ?? null });
|
|
4633
|
+
const warmPrecutTestSeam = (runner as (GateRunner & { warmPrecutCanaryTestSeam?: {
|
|
4634
|
+
config: unknown; hooks: Pick<NonNullable<GateOptions['pod']>, 'warmReplay' | 'warmFinalize' | 'warmQuarantine' | 'warmReuseConfig'>;
|
|
4635
|
+
} }) | undefined)?.warmPrecutCanaryTestSeam;
|
|
4636
|
+
const canaryOpts = warmPrecutTestSeam && opts.pod ? { ...opts, pod: { ...opts.pod, ...warmPrecutTestSeam.hooks } } : opts;
|
|
4637
|
+
const canary = await runWarmPrecutCanary({ opts: canaryOpts, runner: canaryRunner, file: opts.warmPrecutCanary.file,
|
|
4638
|
+
config: warmPrecutTestSeam?.config ?? config,
|
|
4639
|
+
historyPath: verdictFile(opts.ledgerRoot ?? releaseLedgerRoot(), opts.version) });
|
|
4640
|
+
const output = { version: opts.version, commit: opts.commit, samples: canary.samples, decision: canary.decision };
|
|
4641
|
+
console.log(JSON.stringify(output));
|
|
4642
|
+
return 0;
|
|
4643
|
+
}
|
|
4302
4644
|
if (opts !== 'help') {
|
|
4303
4645
|
placement = gateExecutorOf(opts);
|
|
4304
|
-
debug.log('release-loop.gate', 'executor', { version: opts.version, ...placement, requested: opts.executor ?? null
|
|
4646
|
+
debug.log('release-loop.gate', 'executor', { version: opts.version, ...placement, requested: opts.executor ?? null,
|
|
4647
|
+
imagePin: opts.imagePin ?? null, imageCommit: opts.imagePin === 'cut-commit' ? opts.commit : null });
|
|
4305
4648
|
}
|
|
4306
4649
|
if (opts === 'help') {
|
|
4307
|
-
console.log('Usage: bun scripts/release-loop/gate-node.ts --commit <sha> --version <v> [--baseline-version <prev>] [--baseline-commit <sha>] [--remote <ssh-host>] [--remote-mirror <path>] [--pod-pool <pool>] [--pod-shards <n>] [--pod-shard-timeout-seconds <n>] [--recheck-file-limit <n>] [--fresh-shards] [--reroute-shard <pod-N|auto>] [--shard-host <pool-context>] [--executor <pod|host>] [--rehearsal-baseline] [--full] [--json]\nWithout flags, input.commit, input.version and input.previousVersion come from the JSON file at ELANOUS_GRAPH_CONTEXT.\n--executor host (input.gateExecutor) ignores every pod option and sweeps on --remote (input.gateRemote, required).\n--rehearsal-baseline pins source graph bytes only for a direct Pod gate on an isolated rc version.\n--full disables receipt and partial reuse and runs the complete test inventory.');
|
|
4650
|
+
console.log('Usage: bun scripts/release-loop/gate-node.ts --commit <sha> --version <v> [--baseline-version <prev>] [--baseline-commit <sha>] [--remote <ssh-host>] [--remote-mirror <path>] [--pod-pool <pool>] [--precut-canary-file <test-file>] [--pod-shards <n>] [--pod-shard-timeout-seconds <n>] [--recheck-file-limit <n>] [--fresh-shards] [--reroute-shard <pod-N|auto>] [--shard-host <pool-context>] [--executor <pod|host>] [--rehearsal-baseline] [--full] [--json]\n--precut-canary-file runs exactly two one-file warm↔fresh comparisons and stores them in this version\'s gate-verdict warmHistory before the standard gate consumes it; decideWarmReuse applies minMatchSamples.\nWithout flags, input.commit, input.version and input.previousVersion come from the JSON file at ELANOUS_GRAPH_CONTEXT.\n--executor host (input.gateExecutor) ignores every pod option and sweeps on --remote (input.gateRemote, required).\n--rehearsal-baseline pins source graph bytes only for a direct Pod gate on an isolated rc version.\n--full disables receipt and partial reuse and runs the complete test inventory.');
|
|
4308
4651
|
return 0;
|
|
4309
4652
|
}
|
|
4310
4653
|
version = opts.version;
|
|
@@ -4324,7 +4667,15 @@ export async function runGateNode(argv: string[], env: NodeJS.ProcessEnv, runner
|
|
|
4324
4667
|
} catch (error) {
|
|
4325
4668
|
result = { outcome: 'error', commit: '', introduced: [], preexisting: 0, fixed: 0, knownEnv: 0, knownEnvCleared: [], stalledEnv: [], rcNonzeroNoFailures: [], durationMs: Date.now() - start, error: String(error) };
|
|
4326
4669
|
placement ??= requestedGateExecutor(argv, env);
|
|
4670
|
+
} finally {
|
|
4671
|
+
try { await warmControlCleanup?.(); }
|
|
4672
|
+
catch (error) {
|
|
4673
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
4674
|
+
debug.log('pod.warm.control-forward', 'cleanup-failed', { version, reason }, { level: 'error' });
|
|
4675
|
+
if (typeof result !== 'undefined') result = { ...result, outcome: 'error', error: [result.error, reason].filter(Boolean).join('; ') };
|
|
4676
|
+
}
|
|
4327
4677
|
}
|
|
4678
|
+
if (!result) throw new Error('release gate returned without a result');
|
|
4328
4679
|
if (versionPattern.test(version) && result.stalledShards?.length) {
|
|
4329
4680
|
result.autoheals = result.stalledShards.map((shard, index) => {
|
|
4330
4681
|
const shardKey = `pod-${shard.shard}`;
|
|
@@ -4363,5 +4714,6 @@ if (import.meta.main) {
|
|
|
4363
4714
|
// The graph spawns this file as its own process, which inherits no daemon sink — without this every
|
|
4364
4715
|
// `release-loop.gate`/`release.gate`/`pod.pool` event stayed in the file trail and `elanous logs` read 0 rows.
|
|
4365
4716
|
await registerStandaloneLogSink('release-loop');
|
|
4366
|
-
process.exitCode = await runWithReleaseGateTraffic(process.env,
|
|
4717
|
+
process.exitCode = await runWithReleaseGateTraffic(process.env,
|
|
4718
|
+
() => runWithReleasePwaTraffic(process.env, () => runGateNode(process.argv.slice(2), process.env)));
|
|
4367
4719
|
}
|