elanous 0.2.32 → 0.2.34
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/{IVKr3AlRUVLKsw2fh7eXF → L04y73dlwKeTW-GetaTF3}/_buildManifest.js +1 -1
- package/apps/pwa/out/_next/static/chunks/1162.c69abda138c7ce43.js +79 -0
- package/apps/pwa/out/_next/static/chunks/1449-1fede62af84c3e43.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1598-13fc845935e816df.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1639.62c5e16fa20d242f.js +94 -0
- package/apps/pwa/out/_next/static/chunks/{4174.92ed04922709ac9e.js → 1644.35dd25240ae24e4b.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/1661-adb8b2babc9aaf8c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2940-619bfd8ef337c611.js → 1895-60ed0e341c658bd4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{5525.86debf231e181813.js → 2124.ed5bf480cea7f85b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2147.107c0fc6146fb15a.js +23 -0
- package/apps/pwa/out/_next/static/chunks/2202-ef1c9b000c3074fc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2247.1e326638ee43e672.js +115 -0
- package/apps/pwa/out/_next/static/chunks/2268.56e1e3d58180abae.js +8 -0
- package/apps/pwa/out/_next/static/chunks/2469-606819c812414021.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6092.a7f99e68b188ca66.js → 2547.dd27303624cb4da8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{2213.2ccbada92c33ea0f.js → 2554.3b5e200f5f5cfcd6.js} +16 -16
- package/apps/pwa/out/_next/static/chunks/2593.e53c1620c0bb3599.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2690.e96671114222676a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2898-79ee1a5cc0cd9d13.js +1 -0
- package/apps/pwa/out/_next/static/chunks/298.26099a9f65e5c8ff.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5173-233188df2f42c626.js → 2987-30cf9c2eb7b8bdd5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2992.51fc2a7295bd8139.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3541-ce3a5424ed5ad7f1.js → 3001-5a52788c813b6113.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3014-291245fba9df7b5b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3068.ee78201f0251a18d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3083.cd399a17e9365f78.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3103.84470c8445962f04.js +34 -0
- package/apps/pwa/out/_next/static/chunks/{6433.fc573af22bbc9ab7.js → 3110.0a7065f25c841818.js} +17 -17
- package/apps/pwa/out/_next/static/chunks/3117-e1c474683fd1f824.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3121.779fafe3e44612c5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3251-2c17bd5a91f8de2e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/326.3fb32322bb2ddf6d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3411-f87314c6c66ebc73.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6157.0c9af021de4fb31b.js → 3414.172ad4ca2d9b08e8.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/3421-7ff7fbaa156985a7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{4745.67d6f99c245af3f7.js → 3498.cf2a15a8a9e5b926.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/381.f55dc63ea87735e6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3819.163d4777f0a597f6.js +32 -0
- package/apps/pwa/out/_next/static/chunks/{5543.a438b8ab5a13ea47.js → 3837.722e053fb9f321ad.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4129-ab0a6189f1778633.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4361.93d14ba594b3a035.js +104 -0
- package/apps/pwa/out/_next/static/chunks/4364.7a7eb19cceb67d2f.js +286 -0
- package/apps/pwa/out/_next/static/chunks/4406.d71f506f7c3ee9d3.js +149 -0
- package/apps/pwa/out/_next/static/chunks/4461.2ad1514180ae7e29.js +148 -0
- package/apps/pwa/out/_next/static/chunks/4479.bd309cbe0e3842ee.js +64 -0
- package/apps/pwa/out/_next/static/chunks/4522-05966f777d66d13b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4543-9a3e9812e845de65.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4557.f8f1d84a0d90d590.js +74 -0
- package/apps/pwa/out/_next/static/chunks/4589-79b11542179ef58c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4633-9f8f9f88ffbb8f5c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4831.23af4d10c0697b5e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4983.5951b85724f61f2a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{b313b242.cb1265eea908db61.js → 4fa33ebd.21c1bd5b294496e6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{997ce8a5-2ad501927003b41b.js → 5014f970-22bef0b5210f7d60.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{2112-e4efc21a6505d5bc.js → 5028-f0722e6114c313e7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{2898.fd781bc7cf01a7b7.js → 5249.96a848656b582a93.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5311.16e0ffbfa5468b80.js +62 -0
- package/apps/pwa/out/_next/static/chunks/{9407.e49124dedf0f5e72.js → 5446.edf141cff9ef647d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{9002.e5658224e3dc1b6f.js → 5489.ec2dd1b5272a19dc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{1335-6731f392c35216eb.js → 5576-82d3703636d5c4b9.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/559.df87695df637920c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5618-5c88f2f70178c4ad.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5764.05487bf0b73ddb6b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5930.e678826c3f831aab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5dfb57f7.f2e3305326202474.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6053-d893974de92e6198.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2985.39ccdecd36848b12.js → 6086.913a69e6fa3e4a8b.js} +4 -4
- package/apps/pwa/out/_next/static/chunks/6200-cefd6f33f59d765e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6314-edeba650ae686ac9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6316-bc9a9b74d7842d79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6476-13b9779b73528008.js +1 -0
- package/apps/pwa/out/_next/static/chunks/666-04db3ac4c8e3c66f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/670.682e55106ef915a6.js +77 -0
- package/apps/pwa/out/_next/static/chunks/{7862.30a8d6482bb238eb.js → 6740.43de73fc427c9fe2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6816.b7ba9d0a1a4a46a7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/690-fbbcc777f189af37.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6989.0d3d4020e5cb3df6.js +90 -0
- package/apps/pwa/out/_next/static/chunks/{2871.0bca6137343b3f76.js → 7005.1623d2aea78eb85f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7193-cf355cca5ba6c5d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7217-d17393fe7af78fa6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7232.24edbd938a69c8d0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7295cd1e.b4b557908888eeee.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7323.adb02de21991883c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7386.5d8e7e42e818dfb6.js +287 -0
- package/apps/pwa/out/_next/static/chunks/{7502.ccd5b8065545edd0.js → 7431.3051e9b8a4372d2d.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/{3583.a05e88cc8d594628.js → 7448.478958ffbbba0ac0.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7537.ba2208c5c656b8a3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7562.948255cbc3e79d33.js +41 -0
- package/apps/pwa/out/_next/static/chunks/7596-962ae6db6cfc9c83.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7671.cff51beac212a2ba.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7883.3916734ee16d3e58.js +27 -0
- package/apps/pwa/out/_next/static/chunks/{9989.3694300e5f000b14.js → 7952.b192c31b6a0e00cc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/801.5b24e81eded22d1b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8176.8c9a8613f5a01ac1.js +36 -0
- package/apps/pwa/out/_next/static/chunks/819e78a6.fa3dd7f7da19bf61.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8220-78978450c3183d8d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8221-eb60b5bc692a3361.js +1 -0
- package/apps/pwa/out/_next/static/chunks/826.e9a26064f474949e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5307.60f2209e809bd627.js → 8368.014caa8de2f5af34.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8459-a351487a4480f584.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8482-75823d513a4ce151.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8488.8da515248cda3f3a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9221.7d22eaf353a2d4e7.js → 8548.036c344973968847.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8584.388a1779d9b203af.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8637.a55e098e61ffefb8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8817.61bb7ffbba502ecb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9398-6aed28f0a96d0d6f.js → 9131-fce7256c4180c4a5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9207-8e6a12db30373f32.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9263-00ae932c62696a86.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9318.caafc3a130e8769c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/932.da3fb55951882562.js +93 -0
- package/apps/pwa/out/_next/static/chunks/9433.002f8426ac75b6b7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9464.dd631a5a14b1d660.js +24 -0
- package/apps/pwa/out/_next/static/chunks/{3278.2e5d15b53a4091c8.js → 9550.d5f76828f972058c.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/{2534.e85ae21ad0dac829.js → 9739.8d5f36ac67a3b236.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9743.beeabb9d5f86abe4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9843-a23d3f32dc43f715.js +1 -0
- package/apps/pwa/out/_next/static/chunks/994-28211077c7c587d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7a1887c4.f36b88fba31da8fd.js → 9a8cb466.cc9fc9e6e46038aa.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{0bb0ff36.b8d418980951e5ba.js → 9b48c871.204d5d13e7809bd0.js} +7 -7
- package/apps/pwa/out/_next/static/chunks/{409f1e57.d0649fc47691ebfe.js → abb4b98b.163e0f56e84ae35d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-3b4c947f174e33d8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-369d5ce99483f859.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-63c0ed0eb26e5fce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-62802c6003bc85cc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-ecb41560d4e77be9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-7b65ad8130c93e95.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-8755477c15a9060c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-5725447dd972c471.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-b979de0591a69375.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-6d0796bf4633751a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-59c31dc0394e67b4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-12dfc95f6cc5c718.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-25788244e66aa4dd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-a2555dc705466322.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-4fb31eb476dcbad3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-3b4c947f174e33d8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-578729adbeaa40cb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-a1d20fffbec35205.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-4a558f94516b8004.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-9e3834705b86145e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-d7bff0f0905a662c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-cb1c5fe746ea4014.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-9ee320507356582a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66fd183b74835886.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-3272a064a01a9927.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-f281b7a7f6ef2529.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-d45a65f8b7124fcd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-e9c1fe684dd19a66.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-b8322932fc6e34de.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-82c66d9af2290575.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-63c6752009c97b05.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-bb33d42ebc358aec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-7e4c9379197d7214.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-38bf1e9a7001732b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-2e5b752012389d56.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-8c66a3b24a745630.js → page-398c0faa173203f7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-002f53a965b6445c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-7f75997f61a26269.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-3b4c947f174e33d8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-05631753294f23fb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-edd700fd2cc3d7f8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-105f83ecb4594365.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-5823e87d2017f19c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/{page-04d00ae9b0890349.js → page-df3445b207ceceab.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-b51ca09566c03de7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-8aacd5ded5dbac60.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-bae3365a0b3c6a8e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-9ed52b48ff1aa7fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-b6f8cad35fe00946.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-6d25ceda500d2457.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-b6401a5f2c962fe8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/ebf6cf5e.0d2f887b040e80fb.js +122 -0
- package/apps/pwa/out/_next/static/chunks/f09ec7d7-d29abe23edcbb193.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{ebc7d829.264a0bcb6c750b50.js → ffdc3a04.b9c23c1b28dd4a96.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/framework-9322cb8a56ac081c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-2fbab294e49a1561.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-f36729f56621a8f3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-2a62e450b47c6114.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-5e17f7b445f34d43.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-b4263e2190ea7adf.js +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +22 -22
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +22 -22
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +22 -22
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +21 -21
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +21 -21
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +22 -22
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +21 -21
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +22 -22
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +22 -22
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +21 -21
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +22 -22
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +22 -22
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +22 -22
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +22 -22
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +21 -21
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +21 -21
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +22 -22
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +22 -22
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +22 -22
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +21 -21
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +21 -21
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +21 -21
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +22 -22
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +22 -22
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +21 -21
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +21 -21
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +21 -21
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +21 -21
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +22 -22
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +22 -22
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +21 -21
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +22 -22
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +22 -22
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +22 -22
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +22 -22
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +22 -22
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +21 -21
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +22 -22
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +22 -22
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +22 -22
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +22 -22
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +22 -22
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +22 -22
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +22 -22
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +22 -22
- package/bin/elanous.mjs +9 -2
- package/docs/goal-context/README.md +1 -0
- package/graphs/default-loop.yaml +20 -3
- package/graphs/document-loop.yaml +21 -2
- package/graphs/implement-loop.yaml +6 -0
- package/graphs/journey/SCENARIOS.md +33 -0
- package/graphs/journey/recipes.yaml +1 -0
- package/graphs/journey/ta-journey.yaml +84 -0
- package/graphs/journey/units/author/author.yaml +20 -0
- package/graphs/journey/units/author/recipes.yaml +2 -0
- package/graphs/journey/units/green/green.yaml +20 -0
- package/graphs/journey/units/green/recipes.yaml +2 -0
- package/graphs/journey/units/pr/pr.yaml +20 -0
- package/graphs/journey/units/pr/recipes.yaml +2 -0
- package/graphs/journey/units/remember/recipes.yaml +2 -0
- package/graphs/journey/units/remember/remember.yaml +20 -0
- package/graphs/operate-loop.yaml +21 -2
- package/graphs/overlays/stop-heal-ladder.yaml +1 -1
- package/graphs/release-rehearsal/recipes.yaml +1 -1
- package/graphs/release-rehearsal/release-rehearsal.yaml +2 -2
- package/graphs/research-loop.yaml +23 -2
- package/graphs/units/dispatch-pod/dispatch-pod.yaml +50 -0
- package/graphs/units/dispatch-pod/recipes.yaml +14 -0
- package/graphs/units/entry/entry.yaml +50 -0
- package/graphs/units/entry/recipes.yaml +14 -0
- package/graphs/units/harvest/harvest.yaml +25 -0
- package/graphs/units/harvest/recipes.yaml +4 -0
- package/graphs/units/land/land.yaml +45 -0
- package/graphs/units/land/recipes.yaml +3 -0
- package/graphs/units/recipes.yaml +7 -0
- package/graphs/units/recover.yaml +39 -0
- package/graphs/units/reflect/recipes.yaml +3 -0
- package/graphs/units/reflect/reflect.yaml +15 -0
- package/graphs/units/task-agent-recover-review.yaml +17 -0
- package/graphs/units/task-agent-recover-wait.yaml +17 -0
- package/graphs/units/verbs/make-doc.yaml +34 -0
- package/graphs/units/verbs/recipes.yaml +33 -0
- package/graphs/units/verbs/verify.yaml +55 -0
- package/graphs/work/recipes.yaml +2 -0
- package/graphs/work/research-report.yaml +28 -0
- package/package.json +1 -1
- package/scripts/ci-typecheck-changed.ts +69 -16
- package/scripts/docs/generate-graph-anatomy.ts +19 -2
- package/scripts/graph-harvest-unit.ts +22 -0
- package/scripts/graph-units/journey-stub.ts +6 -0
- package/scripts/graph-units/land-enqueue.ts +78 -0
- package/scripts/graph-units/recover-plan.ts +15 -0
- package/scripts/graph-units/reflect-node.ts +88 -0
- package/scripts/graph-units/ta-journey-observe.ts +109 -0
- package/scripts/graph-units/verbs/headless-goal-loop.ts +152 -0
- package/scripts/graph-units/verbs/verify-stage.ts +75 -0
- package/scripts/graph-units/verbs-envelope.ts +139 -0
- package/scripts/hq/standby-snapshot.ts +25 -0
- package/scripts/public-export.ts +43 -1
- package/scripts/ratchets/train-side-invariants.sh +45 -0
- package/scripts/release-loop/auto-approve-node.ts +30 -16
- package/scripts/release-loop/gate-closure.ts +5 -1
- package/scripts/release-loop/gate-diff.ts +1 -1
- package/scripts/release-loop/gate-node.ts +498 -31
- package/scripts/release-loop/gate-partial.ts +3 -3
- package/scripts/release-loop/gate-radius-shadow.ts +138 -0
- package/scripts/release-loop/gate-traffic-mode.ts +128 -0
- package/scripts/release-loop/gate-triage-node.ts +5 -2
- package/scripts/release-loop/known-issues-0.2.33-approved.json +194 -0
- package/scripts/release-loop/known-issues-node.ts +77 -8
- package/scripts/release-loop/preflight.ts +93 -7
- package/scripts/release-loop/publish-node.ts +60 -9
- package/scripts/release-loop/resume-cap.ts +56 -0
- package/scripts/release-loop/resume-release.ts +114 -15
- package/scripts/release-loop/resume.ts +24 -1
- package/scripts/release-loop/tui-sim-node.ts +8 -2
- package/scripts/release-loop/unattended-release.ts +8 -2
- package/src/acp/core-turn-bridge.ts +20 -5
- package/src/acp/reasoning-thought-buffer.ts +45 -0
- package/src/agent-substrate/acp-reviewer.ts +52 -12
- package/src/agent-substrate/review-finding-key.ts +53 -0
- package/src/agent-substrate/self-review-cli.ts +126 -5
- package/src/ask-user-question/pending-questions.ts +1 -1
- package/src/autopilot/pr-manager.ts +192 -43
- package/src/chat/tool-render/dispatch.ts +62 -10
- package/src/cli/control-cli.ts +24 -7
- package/src/cli/guardian-cli.ts +10 -0
- package/src/cli/light-entry-route.ts +35 -0
- package/src/cli/llm-detect-cli.ts +23 -1
- package/src/cli/pr-cli.ts +76 -12
- package/src/cli/pr-land-base-read.ts +2 -0
- package/src/cli/pr-land-overlap-evidence.ts +154 -16
- package/src/cli/pr-land-recovery.ts +64 -0
- package/src/cli/pty-takeover-cli.ts +6 -5
- package/src/cli/release-cli.ts +120 -33
- package/src/cli/release-run-if-ready.ts +29 -28
- package/src/cli/release-single-flight.ts +178 -0
- package/src/cli/seat-cli.ts +37 -0
- package/src/cli/setup-cli.ts +40 -1
- package/src/cli/tasks-cli.ts +149 -5
- package/src/control/control-status.ts +8 -3
- package/src/control-plane/ledger.ts +5 -3
- package/src/control-plane/server.ts +5 -3
- package/src/core-turn/short-question.ts +5 -1
- package/src/dashboard/index.ts +36 -67
- package/src/dashboard/input/chat-main-plain-turn-runtime.ts +3 -0
- package/src/dashboard/input/dashboard-plain-turn-stream.ts +4 -0
- package/src/dashboard/input/seat-autowake.ts +56 -3
- package/src/dashboard/seat-boot-preamble.ts +1 -1
- package/src/dashboard/slash-runtime/ask-clarification.ts +48 -0
- package/src/dashboard/slash-runtime/dashboard-delegated-slashes.ts +68 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +62 -322
- package/src/dashboard/slash-runtime/dashboard-log-slash.ts +208 -0
- package/src/dashboard/turn-preamble.ts +47 -1
- package/src/dashboard/turn-stream-formatter.ts +34 -1
- package/src/dashboard/turn-stream-runtime.ts +2 -0
- package/src/dashboard/turn-typeahead-prompt-zone.ts +67 -0
- package/src/domains/guardian-tick.ts +54 -2
- package/src/domains/outbound-alert.ts +3 -2
- package/src/extractors.ts +3 -1
- package/src/git-fs/retry.ts +4 -0
- package/src/git-fs/runner.ts +2 -0
- package/src/graph-runner/runner.ts +114 -44
- package/src/grok/credential.ts +5 -0
- package/src/harness/harness-cli-command.ts +39 -10
- package/src/harness/harness-incidents.ts +4 -0
- package/src/harness/harness-pod-dispatch.ts +38 -1
- package/src/harness/harness-salvage-cli.ts +14 -2
- package/src/index.ts +192 -8
- package/src/intake-plane/already-built-hint.ts +89 -0
- package/src/knowhow/pack.ts +199 -0
- package/src/light-cli.ts +95 -0
- package/src/llm.ts +55 -9
- package/src/loops/budget.ts +95 -0
- package/src/loops/msg-wake.ts +8 -2
- package/src/loops/orchestrator/traffic.ts +132 -1
- package/src/mss/logging/log-store.ts +4 -5
- package/src/nexus/api/graphs-api.ts +22 -3
- package/src/nexus/api/http-server.ts +1 -1
- package/src/nexus/api/ops-api.ts +12 -0
- package/src/oauth/claude-pty-maturity.ts +66 -0
- package/src/oauth/codex-access-mirror.ts +55 -0
- package/src/oauth/fallback-chain.ts +1 -1
- package/src/onboarding/first-run.ts +5 -3
- package/src/onboarding/non-interactive.ts +64 -13
- package/src/onboarding.ts +10 -2
- package/src/ops/overview-collect.ts +36 -1
- package/src/ops/overview.ts +11 -0
- package/src/pr-tool/census.ts +23 -5
- package/src/pr-tool/inflight-registry.ts +160 -7
- package/src/prompt-library/gpt6-family-addendum.ts +14 -1
- package/src/pty-shell/herdr-agent-report.ts +442 -0
- package/src/pty-shell/pty-control-ipc.ts +5 -5
- package/src/pty-shell/pty-write-outcome.ts +24 -6
- package/src/pty-shell/tui-run-state.ts +117 -3
- package/src/release-loop/checklist.ts +79 -5
- package/src/release-loop/feature-store.ts +19 -2
- package/src/release-loop/placement.ts +10 -3
- package/src/release-loop/rehearsal.ts +205 -25
- package/src/release-loop/rubric.ts +16 -2
- package/src/self-dev/draft-sweep.ts +52 -3
- package/src/self-dev/graph-journey-nodes.ts +10 -6
- package/src/self-dev/graph-journey-route.ts +60 -6
- package/src/self-dev/orchestrate.ts +79 -3
- package/src/self-dev/run-supervisor.ts +107 -12
- package/src/self-dev/run-types.ts +5 -1
- package/src/self-implement/gate-baseline.ts +49 -5
- package/src/self-implement/gate-remote.ts +9 -5
- package/src/self-implement/goal-author-identity.ts +66 -0
- package/src/self-implement/goal-author-preservation-obligation.ts +35 -0
- package/src/self-implement/goal-author.ts +4 -120
- package/src/self-implement/graph-overlay-yaml.ts +17 -2
- package/src/self-implement/graph-unit-flatten.ts +103 -0
- package/src/self-implement/graph-unit-resolver.ts +107 -0
- package/src/self-implement/graph-yaml.ts +151 -1
- package/src/self-implement/headless-elanous-driver.ts +1 -0
- package/src/self-implement/knowhow-pack.ts +103 -0
- package/src/self-implement/orchestrator.ts +8 -0
- package/src/self-implement/pod-warm-pool-profile.ts +45 -0
- package/src/self-implement/review-mustfix-class.ts +203 -0
- package/src/self-implement/run-stop.ts +8 -2
- package/src/self-implement/seams.ts +32 -1
- package/src/self-implement/stop-autoheal.ts +25 -1
- package/src/session/chat.ts +27 -0
- package/src/session-runtime/retry-policy.ts +2 -1
- package/src/skills/tools/schedule-wakeup.ts +66 -17
- package/src/steward/release-day.ts +7 -3
- package/src/steward/triage.ts +37 -4
- package/src/task-agent/harvest-unit.ts +141 -0
- package/src/task-agent/live-moves.ts +98 -12
- package/src/task-agent/parent-host.ts +93 -3
- package/src/task-agent/postland-card.ts +80 -1
- package/src/task-agent/rejudge-on-head.ts +112 -26
- package/src/task-agent/repair-branch-cas.ts +4 -1
- package/src/task-agent/repair-cycle.ts +16 -3
- package/src/task-agent/run-identity.ts +4 -3
- package/src/task-agent/shadow.ts +12 -2
- package/src/task-agent/task-hand.ts +54 -7
- package/src/task-agent/work-order-build.ts +22 -0
- package/src/task-orchestrator/surfaces/pod-account-broker.ts +39 -3
- package/src/task-orchestrator/surfaces/pod-command-job.ts +2 -1
- package/src/task-orchestrator/surfaces/pod-lifecycle-stages.ts +86 -8
- package/src/task-orchestrator/surfaces/pod-pool.ts +73 -17
- package/src/task-orchestrator/surfaces/pod-source-receive.ts +54 -4
- package/src/task-orchestrator/surfaces/pod-warm-worker-pod.ts +26 -3
- package/src/task-orchestrator/surfaces/pod-warm-worker-provider.ts +389 -0
- package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +2 -1
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +223 -38
- package/src/task-orchestrator/surfaces/self-implement.ts +83 -2
- package/src/tool-runtime/truncation-store.ts +4 -0
- package/src/tui-client/dashboard-session.ts +10 -1
- package/src/user-config/coercion.ts +36 -0
- package/src/user-config.ts +18 -41
- package/src/ux-sim/scenario.ts +72 -13
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/1034-8b639cee57b8e4a8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1085-6c2d35526f11f840.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1220.1ff7d5cdc34b1ae5.js +0 -27
- package/apps/pwa/out/_next/static/chunks/1247.5884ec81b39fe2e7.js +0 -287
- package/apps/pwa/out/_next/static/chunks/1302.87681521a7a4661e.js +0 -62
- package/apps/pwa/out/_next/static/chunks/1324-59dae3002413623b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1343-f40b5870ac1eda5a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/146-b6cd56be11dda017.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1654.32de4b324981a188.js +0 -23
- package/apps/pwa/out/_next/static/chunks/1834-98b64172bc1efe77.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1849.1ce38efa6c527d9a.js +0 -149
- package/apps/pwa/out/_next/static/chunks/1913.7cc6fb6055b1ed3f.js +0 -41
- package/apps/pwa/out/_next/static/chunks/2001.6c7e0fd02190886c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2030.41fae98c7e907304.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2099-08b2f2dfba742c9b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2134.ecee03a550ee7250.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2236.66061832181cb745.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2422.b0bd272e7020d7e6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3040.92ff9ffc9f971cb9.js +0 -115
- package/apps/pwa/out/_next/static/chunks/3144.6861096c7b4edef7.js +0 -64
- package/apps/pwa/out/_next/static/chunks/3267c0e6.d34854e7b3984a6e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3771.e928edf2867d28c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3902.e1e71aaac850a045.js +0 -34
- package/apps/pwa/out/_next/static/chunks/4063-03284b5de59965f3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4111.2bfedbb233ad206b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4300-c7ec4eeabbb5f3f7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4483.646d5065199e1b55.js +0 -36
- package/apps/pwa/out/_next/static/chunks/4519.28199ff26b2fd80d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4581.ef2443578054544f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4608-8d894e9cbe4f08bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4639.ae48a01d1bb50a93.js +0 -286
- package/apps/pwa/out/_next/static/chunks/4739.08742ac668aada67.js +0 -77
- package/apps/pwa/out/_next/static/chunks/4882-16cef71d6a1a32be.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4999.c064a4000c2a9e94.js +0 -79
- package/apps/pwa/out/_next/static/chunks/5260-69f35419bca263e7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5272.b92d1b72a15c25fe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5304.04d24756f242a094.js +0 -148
- package/apps/pwa/out/_next/static/chunks/5415-997980f828667736.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5458-16cd7a2b51b74c8c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5572-42c9084f27ca761e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5661-80470aac963d9f36.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5723.421338408b5c30c4.js +0 -74
- package/apps/pwa/out/_next/static/chunks/5809.64c3664267c50bce.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6000.64bc21ac8627f2f5.js +0 -94
- package/apps/pwa/out/_next/static/chunks/6013.39459b9c68eabf5f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/607-06466dff012b03ef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/60fb96c9.360b01d8d4927b7b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6192.a190b989b81e9743.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6323.8672b4817a306928.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6444.7d1fdcb9b09ae71c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6517.4147878ac06d584d.js +0 -90
- package/apps/pwa/out/_next/static/chunks/6532.e34302010b2f0b68.js +0 -32
- package/apps/pwa/out/_next/static/chunks/6544cb48.a87fe20ddf137be2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6546.74bc4ed82f19016d.js +0 -104
- package/apps/pwa/out/_next/static/chunks/6684-e7523ce7c2fad075.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6726.ebdc82feb161431e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6739.00fb6f892e5e392b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6745.934c4921bf15513a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6767.ea607cf0c20a25af.js +0 -24
- package/apps/pwa/out/_next/static/chunks/6843.67bfd020da0926c5.js +0 -93
- package/apps/pwa/out/_next/static/chunks/69-0a2c8209c6d4a158.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6948.1062d4594652b5bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7138-eaf841fc1dc03463.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7174-cda29ee13d7790e7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7304-3c07583f4e405f4f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7371.80c05c653b08e907.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7391.ebae168025c91b6a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7557.e325503a090a4310.js +0 -8
- package/apps/pwa/out/_next/static/chunks/7569-9ce9432c7f10989f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7886-55854585fadeded1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7947-d0759430f8042240.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8018.a32663f04a6fc5f9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8097-9530c03bcc550f02.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8215-1661812b6e9ad71f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8260-19c921a51ca8ec3e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8440.3a5cf5c85aa76db8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8519-4599be2249161e68.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8547.b028b90c5c01ace6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8587-cbe02a953a7f4990.js +0 -1
- package/apps/pwa/out/_next/static/chunks/877-2b977259c4b15655.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9019-0a4b40c50c7314fb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9138.b6e80031afb1d973.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9188-d36e4fc869ddc166.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9230.44f5039e796e4ab4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9279-d28c5b7754d010ae.js +0 -1
- package/apps/pwa/out/_next/static/chunks/939.336acce73e98482c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9610.c07cce4bfcc14ed7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9667-ce78c2ce91050ff9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-339fabb3499da0e1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-504204b58c82c253.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-a9717a67c2eecbaf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-5c9ec30aee62dfbf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-584191533721fa16.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-73c4b4bb3ac7ab5f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-c0f1ca371f710b06.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-46430d45a9e04eac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-3b2ebf1dc64ff0be.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-a27b1e5a0d1b308f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-25654b82094f7d22.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-8ea49a4c6dd2c6e7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-ce5113aefea0a8d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-0e5a41fac1c75396.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-31aaf1ed37c6df9a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-339fabb3499da0e1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-3dcc1fa411e1f4e0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-c69247e7134d329d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-66dc5cd3a0999abe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-26ff7ff651a7217d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-617b3976981efa96.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-d256f5d5d9552823.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-ce8f1907b8a6101c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-33e182a316263116.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-32866485913d6a04.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-7607cca9b84950c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-eeab86176b03dc3b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-5b93dbae3432b996.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-77a6cb90a9cf2b10.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-f47e768b5b242b5a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-eb41aa3367fc2505.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-4d13ab0b8a267e3f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-84dad32ff0fc0831.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-95306f892aa2d0ee.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-940fb6c217f6b470.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-7b675dfd35c36fc9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-8bd0d3cf7665bcfc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-339fabb3499da0e1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-c219f2ed171131be.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-40f8fa21bdae955b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-468f8313e4f1fbd9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-7bbb8fa353a246c4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-4214b855d9233ea8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-936752c7aa58762e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-ec052ad359df1797.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-8305570551091338.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-fad650e4793adb65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-d90e64743826e1d8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-a16bcfd49af86a32.js +0 -1
- package/apps/pwa/out/_next/static/chunks/e5012ffe.f727e23c231f4342.js +0 -122
- package/apps/pwa/out/_next/static/chunks/ea695e3c-1f1d70d740cf3031.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-906af9bd029e166c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-a24847f9f684d469.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-7bdcd8aa7c8397d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-6ba46a5066fc226d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-a8dc6d07591f2a25.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-f13df28c15c55eee.js +0 -1
- package/src/cli/g6a-registration-probe.ts +0 -16
- /package/apps/pwa/out/_next/static/{IVKr3AlRUVLKsw2fh7eXF → L04y73dlwKeTW-GetaTF3}/_ssgManifest.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { spawn, spawnSync } from 'node:child_process';
|
|
3
|
-
import { appendFileSync, chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { appendFileSync, chmodSync, copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { hostname, tmpdir } from 'node:os';
|
|
5
5
|
import { createHash, randomUUID } from 'node:crypto';
|
|
6
6
|
import { dirname, join, resolve } from 'node:path';
|
|
@@ -14,6 +14,7 @@ import { loadEnvKnownFailures, splitKnownEnv } from './env-known-failures';
|
|
|
14
14
|
import { loadGateBatchSafe } from './gate-batch-safe';
|
|
15
15
|
import { baselineFromCutLogs } from './baseline-from-logs';
|
|
16
16
|
import { mergePartialFailures, readPartialPlan, testFileOfId, verifyDocsOnlyCarry } from './gate-partial';
|
|
17
|
+
import { planGateRadiusShadow, shadowActual, writeGateRadiusShadow, type GateRadiusShadowPlan } from './gate-radius-shadow';
|
|
17
18
|
import { logPlanWalltime, planDurations, readFileMemory } from './shard-plan';
|
|
18
19
|
import { defaultGateHostMirrorReaders, resolveGateHostMirror, type GateHostMirror, type GateHostMirrorReaders } from './gate-host-mirror';
|
|
19
20
|
import { isGateSpawnLaneFile, planGateSpawnSeparatedShards } from './spawn-lane';
|
|
@@ -28,15 +29,48 @@ import { POD_BUN_CACHE_HOST_PATH, parseInstallSeconds, podBunCacheVolume } from
|
|
|
28
29
|
import { PodPoolScheduler, parsePodPool, checkPodPool, defaultRemoteRun, type PodPoolMember } from '../../src/task-orchestrator/surfaces/pod-pool.js';
|
|
29
30
|
import { POD_INSTALL_SLOTS_DEFAULT, POD_INSTALL_SLOTS_HOST_PATH, installSlotScript } from '../../src/task-orchestrator/surfaces/pod-install-slots.js';
|
|
30
31
|
import { gateDepsInstallScript, parseGateDeps } from '../../src/task-orchestrator/surfaces/pod-gate-deps.js';
|
|
31
|
-
import { writeWarmJobManifest } from './gate-warm-artifacts.js';
|
|
32
|
+
import { warmReferenceFromJobArtifacts, writeWarmJobManifest } from './gate-warm-artifacts.js';
|
|
33
|
+
import { assessWarmWorkerShadow, parseWarmWorkerReply, type WarmWorkerCommand, type WarmWorkerFileResult, type WarmWorkerKey } from '../../src/task-orchestrator/surfaces/pod-warm-worker-protocol.js';
|
|
34
|
+
import type { WarmPodTransportResult } from '../../src/task-orchestrator/surfaces/pod-warm-worker-pod.js';
|
|
35
|
+
import { requestWarmPodReplay, finalizeWarmPodReplay, quarantineWarmPodReplay, type WarmReplayResult } from '../../src/task-orchestrator/surfaces/pod-warm-worker-provider.js';
|
|
36
|
+
import { resolveWarmPoolProfile } from '../../src/self-implement/pod-warm-pool-profile.js';
|
|
37
|
+
import { decideWarmReuse, type WarmReuseSwitchConfig, type WarmReuseEvidence } from '../../src/task-orchestrator/surfaces/pod-warm-switch.js';
|
|
38
|
+
import { getUserConfig } from '../../src/user-config.js';
|
|
32
39
|
import { gateShardsPath, recordGateShardsVerdict, writeGateShards, type GateShard } from '../../src/release-loop/gate-shards.js';
|
|
33
40
|
import { defaultKubectl } from '../../src/task-orchestrator/surfaces/self-implement-pod.js';
|
|
34
41
|
import { executeTuiNodeCheck, type ExecutedTuiCheck } from './tui-node-checks.js';
|
|
42
|
+
import { runWithReleaseGateTraffic } from './gate-traffic-mode.js';
|
|
35
43
|
import { emitPodLifecycle, gatePodLifecycleEvent, gatePodOutcome } from '../../src/task-orchestrator/surfaces/pod-lifecycle-stages.js';
|
|
36
44
|
import { gateHostReservationRoot, reserveReleaseGateHosts } from '../../src/release-loop/gate-host-reservation.js';
|
|
37
45
|
import { normRoot } from '../../src/instance/resolve.js';
|
|
38
46
|
|
|
47
|
+
export interface WarmShadowObservation {
|
|
48
|
+
files: string[];
|
|
49
|
+
attemptId: string;
|
|
50
|
+
reason: string;
|
|
51
|
+
status: 'observed' | 'fallback' | 'unmeasured';
|
|
52
|
+
quarantined: 'yes' | 'no' | 'failed' | 'unavailable';
|
|
53
|
+
finalized?: boolean;
|
|
54
|
+
/** Exact producer identity; absent on an old verdict means the sample cannot enable reuse. */
|
|
55
|
+
cutSha?: string;
|
|
56
|
+
baselineSha?: string;
|
|
57
|
+
workerKey?: WarmWorkerKey;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** A provider may fill only lease identity; it cannot rewrite the Job attempt being compared. */
|
|
61
|
+
export function warmCommandMatchesRequest(command: WarmWorkerCommand, request: Omit<WarmWorkerCommand, 'leaseId' | 'generation' | 'workerId' | 'key'>): boolean {
|
|
62
|
+
return command.protocol === request.protocol && command.runId === request.runId
|
|
63
|
+
&& command.attemptId === request.attemptId && command.cutSha === request.cutSha
|
|
64
|
+
&& command.baselineSha === request.baselineSha && command.deadlineAt === request.deadlineAt
|
|
65
|
+
&& command.envProfile === request.envProfile && command.files.length === request.files.length
|
|
66
|
+
&& command.files.every((file, index) => file === request.files[index]);
|
|
67
|
+
}
|
|
68
|
+
|
|
39
69
|
interface CommandResult { rc: number; output: string; passedIds?: string[]; stalledEnv?: Array<{ file: string; reason: 'no-output' }>; timedOut?: boolean;
|
|
70
|
+
/** Complete, source-bound one-file Job result for shadow comparison; absent means unmeasured. */
|
|
71
|
+
warmReference?: WarmWorkerFileResult | null;
|
|
72
|
+
warmAttemptId?: string;
|
|
73
|
+
warmShadow?: WarmShadowObservation;
|
|
40
74
|
/** GATE-POD-STALL-FAILFAST ① — shard Pods whose tests the mid-run watch aborted in this sweep. */
|
|
41
75
|
abortedShards?: number;
|
|
42
76
|
/** Each contributing Pod leaf already passed failuresOf; repeated printed ids cannot add new failures. */
|
|
@@ -76,6 +110,22 @@ export interface GateOptions {
|
|
|
76
110
|
recheckFileLimit?: number;
|
|
77
111
|
/** freshShards — GATE-SHARD-RESUME off: every shard starts a Pod even when this logDir already holds its result. */
|
|
78
112
|
pod?: { pool: string; shards?: number; shardTimeoutSeconds?: number; durationSource?: string; bunCache?: string; freshShards?: boolean;
|
|
113
|
+
/** Opt-in gate shadow: provider fills the fenced lease identity. The Job remains authoritative. */
|
|
114
|
+
warmReplay?: (input: Omit<WarmWorkerCommand, 'leaseId' | 'generation' | 'workerId' | 'key'>) => Promise<{ command: WarmWorkerCommand | null; transport: WarmPodTransportResult }>;
|
|
115
|
+
warmQuarantine?: (command: WarmWorkerCommand, reason: string) => Promise<void>;
|
|
116
|
+
warmFinalize?: (command: WarmWorkerCommand) => Promise<void>;
|
|
117
|
+
/** Set only on the cut recheck; baseline Jobs never consume the cut worker. */
|
|
118
|
+
warmCutSha?: string;
|
|
119
|
+
warmBaselineSha?: string;
|
|
120
|
+
warmReuseConfig?: WarmReuseSwitchConfig;
|
|
121
|
+
/** At most three cut isolation files may request a shadow replay per verdict. */
|
|
122
|
+
warmEligible?: boolean;
|
|
123
|
+
/** An explicitly excluded file cannot race for the shared replay budget. */
|
|
124
|
+
warmReplayOwner?: boolean;
|
|
125
|
+
/** Internal budget shared by every recheck in one gate verdict, including timeout retries. */
|
|
126
|
+
warmShadowScope?: { requested: boolean; attempts?: number };
|
|
127
|
+
/** Internal shadow wait cap; a stuck provider cannot hold the Job verdict (tests may lower it). */
|
|
128
|
+
warmShadowTimeoutMs?: number;
|
|
79
129
|
/** Maximum concurrent file-isolation fallback Jobs across the entire sweep; omitted = 4. */
|
|
80
130
|
isolationConcurrency?: number;
|
|
81
131
|
/** GATE-SKIP-KNOWN-RETRY — failure ids the previous release already had (and env-known ones): a shard that fails only
|
|
@@ -124,6 +174,16 @@ export interface GateOptions {
|
|
|
124
174
|
/** GATE-WARM-BOOT-SCAN — observation only: the sweep's test selection (list · filter · duration plan · shard plan) time. */
|
|
125
175
|
onSelectionTimed?: (timed: { ms: number; files: number }) => void };
|
|
126
176
|
}
|
|
177
|
+
|
|
178
|
+
/** Gate-profile opt-in wires the producer only for the process entry, never a test runner seam. */
|
|
179
|
+
export function withWarmGateHooks(opts: GateOptions, config: unknown, injectedRunner = false): GateOptions {
|
|
180
|
+
if (injectedRunner || !opts.pod || resolveWarmPoolProfile(config) !== 'gate') return opts;
|
|
181
|
+
const warmReuseConfig = config && typeof config === 'object' && 'pod' in config
|
|
182
|
+
&& config.pod && typeof config.pod === 'object' && 'warmReuse' in config.pod
|
|
183
|
+
? config.pod.warmReuse as WarmReuseSwitchConfig : undefined;
|
|
184
|
+
return { ...opts, pod: { ...opts.pod, warmReplay: requestWarmPodReplay,
|
|
185
|
+
warmFinalize: finalizeWarmPodReplay, warmQuarantine: quarantineWarmPodReplay, warmReuseConfig } };
|
|
186
|
+
}
|
|
127
187
|
export interface GateResult {
|
|
128
188
|
outcome: 'ok' | 'regression' | 'error';
|
|
129
189
|
commit: string;
|
|
@@ -137,6 +197,8 @@ export interface GateResult {
|
|
|
137
197
|
rcNonzeroNoFailures: Array<{ file: string; rc: number; pass: number; baseline: 'same' | 'clean' | 'missing' }>;
|
|
138
198
|
/** Files left unmeasured after isolated recheck; these cannot seed a reusable baseline. */
|
|
139
199
|
unmeasuredRecheckFiles?: string[];
|
|
200
|
+
/** Shadow only: Job verdict is unchanged even on a worker mismatch or failed quarantine. */
|
|
201
|
+
warmShadow?: WarmShadowObservation[];
|
|
140
202
|
/** GATE-POD-STALL-FAILFAST ① — cut sweep shard Pods ended by the mid-run abort (their files went the timeout way). */
|
|
141
203
|
abortedShards?: number;
|
|
142
204
|
baselineSource?: 'ledger' | 'instance' | 'cut-logs' | 'swept';
|
|
@@ -149,6 +211,10 @@ export interface GateResult {
|
|
|
149
211
|
selectionMs?: number;
|
|
150
212
|
error?: string;
|
|
151
213
|
stalledShards?: Array<{ shard: number; files: string[]; reason: 'incomplete' | 'no-output' | 'job-failed' | 'exit-code' | 'unattributed' | 'timeout'; lastFile?: string; summaryFailures?: number; namedFailures?: number; detail?: string;
|
|
214
|
+
/** Pool member that ran the final unmeasured attempt; enables a bounded cross-host tail remeasure. */
|
|
215
|
+
lastHost?: string;
|
|
216
|
+
/** Failure of the optional cross-host tail attempt; original stall fields remain authoritative. */
|
|
217
|
+
tailRemeasure?: { reason: string; host?: string; detail?: string };
|
|
152
218
|
/** GATE-POD-STALL-FAILFAST — the shard (and its one fresh retry) printed no test output within the stall window: unmeasured. */
|
|
153
219
|
stall?: { seconds: number; sinceCheckoutMs: number } }>;
|
|
154
220
|
partialSummary?: { pass: number; fail: number; errors: number; ran: number; files: number };
|
|
@@ -169,6 +235,124 @@ const versionPattern = /^\d+\.\d+\.\d+(?:-(?:rc|alpha|beta)\.\d+)?$/;
|
|
|
169
235
|
const failureFile = (root: string, version: string) => join(root, 'release', version, 'gate-failures.json');
|
|
170
236
|
/** Judged identities live beside the machine release ledger; gate-failures.json keeps its raw baseline schema. */
|
|
171
237
|
const verdictFile = (root: string, version: string) => join(root, 'release', version, 'gate-verdict.json');
|
|
238
|
+
const WARM_VERDICT_HISTORY_LIMIT = 5;
|
|
239
|
+
const WARM_REPLAY_BUDGET = 3;
|
|
240
|
+
interface WarmVerdictSample { version: string; commit: string; baselineCommit: string; warmShadow: WarmShadowObservation[] }
|
|
241
|
+
const recordValue = (value: unknown): Record<string, unknown> | null =>
|
|
242
|
+
value !== null && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : null;
|
|
243
|
+
/** The verdict is atomically replaced; carry a bounded prior-verdict window in that same SSOT. */
|
|
244
|
+
function readWarmVerdictHistory(path: string): WarmVerdictSample[] | undefined {
|
|
245
|
+
if (!existsSync(path)) return [];
|
|
246
|
+
try {
|
|
247
|
+
const current = recordValue(JSON.parse(readFileSync(path, 'utf8')));
|
|
248
|
+
if (!current || !Array.isArray(current.warmHistory)) return undefined;
|
|
249
|
+
const history = [...current.warmHistory, { version: current.version, commit: current.commit,
|
|
250
|
+
baselineCommit: current.baselineCommit, warmShadow: current.warmShadow }].slice(-WARM_VERDICT_HISTORY_LIMIT);
|
|
251
|
+
if (history.some((entry) => { const row = recordValue(entry);
|
|
252
|
+
return !row || typeof row.version !== 'string' || typeof row.commit !== 'string'
|
|
253
|
+
|| typeof row.baselineCommit !== 'string' || !Array.isArray(row.warmShadow); })) return undefined;
|
|
254
|
+
return history as WarmVerdictSample[];
|
|
255
|
+
} catch { return undefined; }
|
|
256
|
+
}
|
|
257
|
+
/** Every counted match has a measured, finalized worker with this exact cut, baseline and lock. */
|
|
258
|
+
export function warmEvidenceFromVerdicts(history: readonly WarmVerdictSample[] | undefined,
|
|
259
|
+
version: string, head: string, baseline: string, lock: string): WarmReuseEvidence | undefined {
|
|
260
|
+
if (!history?.length || !lock) return undefined;
|
|
261
|
+
const rows = history.flatMap((verdict) => {
|
|
262
|
+
if (verdict.version !== version || verdict.commit !== head || verdict.baselineCommit !== baseline
|
|
263
|
+
|| !Array.isArray(verdict.warmShadow)) return [null];
|
|
264
|
+
return verdict.warmShadow.length ? verdict.warmShadow : [null];
|
|
265
|
+
});
|
|
266
|
+
const attempts = new Set<string>();
|
|
267
|
+
const key = rows.at(-1)?.workerKey;
|
|
268
|
+
if (rows.some((entry) => {
|
|
269
|
+
if (entry === null || !recordValue(entry) || !Array.isArray(entry.files) || !recordValue(entry.workerKey)) return true;
|
|
270
|
+
if (typeof entry.attemptId !== 'string' || !entry.attemptId || attempts.has(entry.attemptId)
|
|
271
|
+
|| entry.files.length !== 1 || typeof entry.files[0] !== 'string'
|
|
272
|
+
|| !/^(?:[\w.-]+\/)*[\w.-]+\.test\.[cm]?[jt]sx?$/.test(entry.files[0])
|
|
273
|
+
|| entry.files[0].split('/').some((part) => part === '.' || part === '..')) return true;
|
|
274
|
+
attempts.add(entry.attemptId);
|
|
275
|
+
return entry.cutSha !== head || entry.baselineSha !== baseline || entry.workerKey?.lockHash !== lock
|
|
276
|
+
|| typeof entry.workerKey.context !== 'string' || !entry.workerKey.context
|
|
277
|
+
|| typeof entry.workerKey.imageDigest !== 'string' || !entry.workerKey.imageDigest
|
|
278
|
+
|| typeof entry.workerKey.architecture !== 'string' || !entry.workerKey.architecture
|
|
279
|
+
|| entry.workerKey.context !== key?.context || entry.workerKey.imageDigest !== key?.imageDigest
|
|
280
|
+
|| entry.workerKey.architecture !== key?.architecture
|
|
281
|
+
|| (entry.reason === 'match' && (entry.status !== 'observed' || entry.finalized !== true || entry.quarantined !== 'no'));
|
|
282
|
+
})) return undefined;
|
|
283
|
+
const recentMismatchCount = rows.filter((entry) => entry!.reason !== 'match').length;
|
|
284
|
+
let consecutiveMatchSamples = 0;
|
|
285
|
+
for (const row of [...rows].reverse()) {
|
|
286
|
+
if (row!.status !== 'observed' || row!.reason !== 'match' || row!.finalized !== true) break;
|
|
287
|
+
consecutiveMatchSamples++;
|
|
288
|
+
}
|
|
289
|
+
const latest = rows.at(-1)!;
|
|
290
|
+
return { consecutiveMatchSamples, recentMismatchCount,
|
|
291
|
+
leaseValid: latest!.finalized === true,
|
|
292
|
+
canaryPassed: latest!.status === 'observed' && latest!.reason === 'match' && latest!.finalized === true,
|
|
293
|
+
// A release cut is the pinned main commit; baselineSha is checked separately above.
|
|
294
|
+
expected: { head, main: head, lock }, worker: { head: latest!.cutSha, main: latest!.cutSha, lock: latest!.workerKey!.lockHash } };
|
|
295
|
+
}
|
|
296
|
+
/** Live reuse is serial and opt-in. Any uncertain lease, reply, or cleanup returns to the fresh Job. */
|
|
297
|
+
async function tryWarmGateAdoption(file: string, cutSha: string, baselineSha: string, expectedKey: WarmWorkerKey,
|
|
298
|
+
pod: NonNullable<GateOptions['pod']>): Promise<CommandResult | undefined> {
|
|
299
|
+
if (!pod.warmReplay || !pod.warmFinalize || !pod.warmQuarantine || !pod.warmShadowScope
|
|
300
|
+
|| (pod.warmShadowScope.attempts ?? 0) >= WARM_REPLAY_BUDGET) return undefined;
|
|
301
|
+
pod.warmShadowScope.attempts = (pod.warmShadowScope.attempts ?? 0) + 1;
|
|
302
|
+
pod.warmShadowScope.requested = true;
|
|
303
|
+
const waitMs = pod.warmShadowTimeoutMs ?? 150_000;
|
|
304
|
+
const deadline = Date.now() + waitMs;
|
|
305
|
+
const request = { protocol: 1 as const, runId: randomUUID(), attemptId: randomUUID(), cutSha,
|
|
306
|
+
baselineSha, deadlineAt: deadline - 1_000, files: [file], envProfile: 'gate-v1' };
|
|
307
|
+
let command: WarmWorkerCommand | null = null;
|
|
308
|
+
let timedOut = false;
|
|
309
|
+
const bounded = async <T>(operation: () => Promise<T>): Promise<T> => {
|
|
310
|
+
const remainingMs = deadline - Date.now();
|
|
311
|
+
if (remainingMs <= 0) throw new Error('warm adoption timeout');
|
|
312
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
313
|
+
try { return await Promise.race([Promise.resolve().then(operation), new Promise<never>((_, reject) => {
|
|
314
|
+
timer = setTimeout(() => reject(new Error('warm adoption timeout')), remainingMs);
|
|
315
|
+
})]); } finally { if (timer) clearTimeout(timer); }
|
|
316
|
+
};
|
|
317
|
+
try {
|
|
318
|
+
const pending = Promise.resolve().then(() => pod.warmReplay!(request));
|
|
319
|
+
void pending.then((late) => {
|
|
320
|
+
if (timedOut && late.command) void pod.warmQuarantine!(late.command, 'late-adoption-after-timeout')
|
|
321
|
+
.catch((error) => debug.log('release-loop.gate', 'warm-adoption-late-quarantine-failed', { file, error: String(error) }));
|
|
322
|
+
}, () => undefined);
|
|
323
|
+
let replay: WarmReplayResult;
|
|
324
|
+
try { replay = await bounded(() => pending); }
|
|
325
|
+
catch (error) { timedOut = true; throw error; }
|
|
326
|
+
command = replay.command;
|
|
327
|
+
if (!command || replay.transport.status !== 'observed' || replay.transport.reason !== 'complete'
|
|
328
|
+
|| !warmCommandMatchesRequest(command, request) || command.key.context !== expectedKey.context
|
|
329
|
+
|| command.key.imageDigest !== expectedKey.imageDigest || command.key.lockHash !== expectedKey.lockHash
|
|
330
|
+
|| command.key.architecture !== expectedKey.architecture) throw new Error('warm adoption lease or identity');
|
|
331
|
+
const reply = parseWarmWorkerReply(replay.transport.reply);
|
|
332
|
+
const part = reply?.files[0];
|
|
333
|
+
if (!reply || !part || reply.files.length !== 1 || part.file !== file || part.rc !== 0 || part.junitComplete !== true
|
|
334
|
+
|| part.failureIds.length !== 0 || part.errorIds.length !== 0
|
|
335
|
+
|| reply.runId !== command.runId || reply.attemptId !== command.attemptId || reply.cutSha !== cutSha
|
|
336
|
+
|| reply.baselineSha !== baselineSha || reply.leaseId !== command.leaseId || reply.generation !== command.generation
|
|
337
|
+
|| reply.workerId !== command.workerId || reply.envProfile !== command.envProfile
|
|
338
|
+
|| reply.key.context !== command.key.context || reply.key.imageDigest !== command.key.imageDigest
|
|
339
|
+
|| reply.key.lockHash !== command.key.lockHash || reply.key.architecture !== command.key.architecture
|
|
340
|
+
|| reply.finishedAt < reply.startedAt || reply.finishedAt > command.deadlineAt) throw new Error('warm adoption result');
|
|
341
|
+
await bounded(() => pod.warmFinalize!(command!));
|
|
342
|
+
debug.log('release-loop.gate', 'warm-adopted-recheck', { file, cutSha, attemptId: command.attemptId });
|
|
343
|
+
return { rc: 0, output: `${file}:\n(pass) warm isolated recheck\n1 pass\n0 fail\nRan 1 test across 1 file.\n` };
|
|
344
|
+
} catch (error) {
|
|
345
|
+
if (command) {
|
|
346
|
+
let quarantineTimer: ReturnType<typeof setTimeout> | undefined;
|
|
347
|
+
try { await Promise.race([pod.warmQuarantine!(command, 'adoption-fallback'),
|
|
348
|
+
new Promise<never>((_, reject) => { quarantineTimer = setTimeout(() => reject(new Error('warm quarantine timeout')), 10_000); })]); }
|
|
349
|
+
catch (fenceError) { debug.log('release-loop.gate', 'warm-adoption-quarantine-failed', { file, error: String(fenceError) }); }
|
|
350
|
+
finally { if (quarantineTimer) clearTimeout(quarantineTimer); }
|
|
351
|
+
}
|
|
352
|
+
debug.log('release-loop.gate', 'warm-adoption-fresh-job', { file, reason: String(error) });
|
|
353
|
+
return undefined;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
172
356
|
const quote = (s: string) => `'${s.replaceAll("'", "'\\''")}'`;
|
|
173
357
|
const defaultRemoteMirror = '~/mirror/elanous-agent.git';
|
|
174
358
|
|
|
@@ -958,6 +1142,16 @@ class StalledPodShards extends Error {
|
|
|
958
1142
|
}
|
|
959
1143
|
}
|
|
960
1144
|
|
|
1145
|
+
class WarmRecheckIncomplete extends Error {
|
|
1146
|
+
constructor(message: string, readonly file: string, readonly shadow: WarmShadowObservation) { super(message); }
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
class SettledRecheckFailures extends Error {
|
|
1150
|
+
constructor(readonly failures: unknown[]) {
|
|
1151
|
+
super(failures[0] instanceof Error ? failures[0].message : String(failures[0]));
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
|
|
961
1155
|
/** Isolated re-runs (host stall re-run · cut and baseline re-checks) are time-limited; sweeps and installs keep their full time. */
|
|
962
1156
|
export const GATE_ISOLATED_TIMEOUT_MS = 300_000;
|
|
963
1157
|
/** Default final recheck window size; overflow continues in later windows. */
|
|
@@ -1212,6 +1406,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1212
1406
|
let abortedShards = 0;
|
|
1213
1407
|
// GATE-MULTI-HOST: members found unreachable during this gate — skipped for placement while any other member is left.
|
|
1214
1408
|
const downMembers = new Set<string>();
|
|
1409
|
+
let tailPlacedHost: string | undefined;
|
|
1215
1410
|
const multiHost = poolScheduler.members.length > 1;
|
|
1216
1411
|
const probeMember = pod.probeMember ?? (poolOverride ? undefined : defaultGateMemberProbe);
|
|
1217
1412
|
const memberUnreadableSeconds = pod.memberUnreadableSeconds ?? GATE_MEMBER_UNREADABLE_SECONDS_DEFAULT;
|
|
@@ -1287,12 +1482,13 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1287
1482
|
/** The gate's scheduler, seen through one shard: a slot wait and the Pod start become row states. */
|
|
1288
1483
|
// GATE-MEM-ADMIT: `placed` releases the shard's admission once a member is chosen — from then on the Job is
|
|
1289
1484
|
// Pending/Running in the cluster and the next admission reading counts it, so holding the lease would count it twice.
|
|
1290
|
-
const trackedPool = (shard: number, retry: boolean, placed: (member: NonNullable<Awaited<ReturnType<PodPoolScheduler['tryAcquire']>>>) => void = () => {}): PodPoolScheduler => ({
|
|
1485
|
+
const trackedPool = (shard: number, retry: boolean, placed: (member: NonNullable<Awaited<ReturnType<PodPoolScheduler['tryAcquire']>>>) => void = () => {}, skipMembers: ReadonlySet<string> = new Set()): PodPoolScheduler => ({
|
|
1291
1486
|
members: poolScheduler.members,
|
|
1292
1487
|
tryAcquire: async (...[skip, onEmpty, mode]: Parameters<PodPoolScheduler['tryAcquire']>) => {
|
|
1293
1488
|
// GATE-MULTI-HOST: a lost member is skipped while another member is left (all lost = the old behaviour).
|
|
1294
1489
|
const anyLeft = poolScheduler.members.some((m) => !downMembers.has(m.context));
|
|
1295
|
-
const
|
|
1490
|
+
const excluded = new Set([...(skip ?? []), ...skipMembers, ...(downMembers.size && anyLeft ? downMembers : [])]);
|
|
1491
|
+
const member = await poolScheduler.tryAcquire(excluded.size ? excluded : undefined, onEmpty, mode);
|
|
1296
1492
|
if (member) placed(member);
|
|
1297
1493
|
if (member) markShard(shard, { state: retry ? 'retry' : 'running', startedAt: board[shard]?.startedAt ?? new Date().toISOString(), host: member.context }, ['waitReason']);
|
|
1298
1494
|
else markShard(shard, { waitReason: 'Pod 자리 대기' });
|
|
@@ -1300,7 +1496,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1300
1496
|
},
|
|
1301
1497
|
release: (member: Parameters<PodPoolScheduler['release']>[0]) => poolScheduler.release(member),
|
|
1302
1498
|
}) as unknown as PodPoolScheduler;
|
|
1303
|
-
const runShard = async (paths: string[], shard: number, depth = 0, branch = '', retriedNoOutput = false, fresh = pod.freshShards === true, parentAttempt?: string, hostRetries = 0, batchIsolation = false): Promise<{ runs: ShardRun[]; stalled: NonNullable<GateResult['stalledShards']> }> => {
|
|
1499
|
+
const runShard = async (paths: string[], shard: number, depth = 0, branch = '', retriedNoOutput = false, fresh = pod.freshShards === true, parentAttempt?: string, hostRetries = 0, batchIsolation = false, skipMembers: ReadonlySet<string> = new Set(), singleShotTail = false): Promise<{ runs: ShardRun[]; stalled: NonNullable<GateResult['stalledShards']> }> => {
|
|
1304
1500
|
// 파일별 소요는 junit 으로 남긴다 — 콘솔 요약(판정 원천)은 그대로이고, 느린 시험 목록(K10 D4)·계층 분리(D2)의 자가 된다.
|
|
1305
1501
|
// GATE-TIMEOUT-HEAL: parts (each with its own junit) under a soft deadline inside this shard's own Job deadline.
|
|
1306
1502
|
const shardDeadline = shardDeadlineSeconds(batchIsolation ? Math.max(...paths.map((file) => estimated([file]))) * Math.ceil(paths.length / batchWidth) : estimated(paths), deadlineSeconds);
|
|
@@ -1356,7 +1552,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1356
1552
|
if (descent === 'retry') {
|
|
1357
1553
|
// «descend» is only the way down; «resumed» comes from each leaf whose log reads cleanly (review r4).
|
|
1358
1554
|
debug.log('release-loop.gate', 'pod-shard-descend', { shard, branch, files: paths.length, via: 'retry' });
|
|
1359
|
-
return runShard(paths, shard, depth, `${branch}-retry`, true, undefined, attemptId, hostRetries, batchIsolation);
|
|
1555
|
+
return runShard(paths, shard, depth, `${branch}-retry`, true, undefined, attemptId, hostRetries, batchIsolation, skipMembers);
|
|
1360
1556
|
}
|
|
1361
1557
|
if (descent === 'split') {
|
|
1362
1558
|
output = undefined;
|
|
@@ -1397,7 +1593,10 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1397
1593
|
} : {}) }) : undefined;
|
|
1398
1594
|
const placed = admitted.release;
|
|
1399
1595
|
const job = await podCommand({
|
|
1400
|
-
pool: pod.pool, poolScheduler: trackedPool(shard, depth > 0 || branch !== '', (member) => {
|
|
1596
|
+
pool: pod.pool, poolScheduler: trackedPool(shard, depth > 0 || branch !== '', (member) => {
|
|
1597
|
+
stamp('poolPlaced'); jobContext = member.context; placedOn = member.context; placed();
|
|
1598
|
+
if (branch === '-tail') tailPlacedHost = member.context;
|
|
1599
|
+
}, skipMembers), clone: true, source: { kind: 'commit', sha: commit }, deadlineSeconds: shardDeadline,
|
|
1401
1600
|
...gateHostMirror(pod),
|
|
1402
1601
|
...(multiHost ? { statusUnreadableMs: memberUnreadableSeconds * 1000 } : {}),
|
|
1403
1602
|
...(bunCache ? { bunCache } : {}),
|
|
@@ -1560,14 +1759,14 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1560
1759
|
// A reused result that the current tool cannot read cleanly is not a verdict: run that shard for real.
|
|
1561
1760
|
if (reused && reason) {
|
|
1562
1761
|
debug.log('release-loop.gate', 'pod-shard-resume-miss', { shard, branch, reason });
|
|
1563
|
-
return runShard(paths, shard, depth, branch, retriedNoOutput, true, parentAttempt, hostRetries, batchIsolation);
|
|
1762
|
+
return runShard(paths, shard, depth, branch, retriedNoOutput, true, parentAttempt, hostRetries, batchIsolation, skipMembers, singleShotTail);
|
|
1564
1763
|
}
|
|
1565
1764
|
// GATE-MULTI-HOST: the Job failed because its member is gone (launch failed · status unreadable) — not the shard's
|
|
1566
1765
|
// fault, so no split: the same files run again on the members left. Bounded by the roster size.
|
|
1567
|
-
if (jobFailed && placedOn && hostRetries < poolScheduler.members.length && memberLost(placedOn, shard, jobError)
|
|
1766
|
+
if (!singleShotTail && jobFailed && placedOn && hostRetries < poolScheduler.members.length && memberLost(placedOn, shard, jobError)
|
|
1568
1767
|
&& poolScheduler.members.some((m) => !downMembers.has(m.context))) {
|
|
1569
1768
|
debug.log('release-loop.gate', 'pod-shard-host-retry', { shard, branch, files: paths.length, lostMember: placedOn, attempt: hostRetries + 1 }, { level: 'warn' });
|
|
1570
|
-
return runShard(paths, shard, depth, `${branch}-h${hostRetries}`, retriedNoOutput, undefined, attemptId, hostRetries + 1, batchIsolation);
|
|
1769
|
+
return runShard(paths, shard, depth, `${branch}-h${hostRetries}`, retriedNoOutput, undefined, attemptId, hostRetries + 1, batchIsolation, skipMembers, singleShotTail);
|
|
1571
1770
|
}
|
|
1572
1771
|
// GATE-TIMEOUT-HEAL ④: a Pod that died before any test ran (no outbox · jobExitCode≠0 — 0.2.19: isolated retries
|
|
1573
1772
|
// ending with exit 5, the source checkout) gets the same one fresh retry as «no output» instead of stalling the sweep.
|
|
@@ -1575,13 +1774,18 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1575
1774
|
const diedBeforeTests = reason === 'job-failed' && output === undefined && !jobFailed && jobExitCode !== undefined && jobExitCode !== 0
|
|
1576
1775
|
&& jobExitCode !== 137 && (paths.length === 1 || jobExitCode === 5);
|
|
1577
1776
|
if ((reason === 'no-output' || diedBeforeTests) && !retriedNoOutput) {
|
|
1578
|
-
const retry = await runShard(paths, shard, depth, `${branch}-retry`, true, undefined, attemptId, hostRetries, batchIsolation);
|
|
1777
|
+
const retry = await runShard(paths, shard, depth, `${branch}-retry`, true, undefined, attemptId, hostRetries, batchIsolation, skipMembers);
|
|
1579
1778
|
debug.log('release-loop.gate', 'pod-shard-retry', { shard, files: paths, reason, outcome: retry.stalled[0]?.reason ?? 'ok' });
|
|
1580
1779
|
return retry;
|
|
1581
1780
|
}
|
|
1582
1781
|
// GATE-POD-STALL-FAILFAST: a stall after the fresh retry is unmeasured — no split, no isolation: smaller Pods on the
|
|
1583
1782
|
// same stalled node only re-measure the stall (10-10: retries and splits repeated it for 2h+).
|
|
1584
|
-
if (stall && reason === 'no-output') return { runs: [], stalled: [{ shard, files: paths, reason, stall }] };
|
|
1783
|
+
if (stall && reason === 'no-output') return { runs: [], stalled: [{ shard, files: paths, reason, stall, ...(placedOn ? { lastHost: placedOn } : {}) }] };
|
|
1784
|
+
// GATE-STALLED-TAIL-REMEASURE: the alternate-host probe is a single observation. Do not split, isolate, or
|
|
1785
|
+
// retry its failed result; the sweep retains the original stall and records this attempt separately.
|
|
1786
|
+
if (singleShotTail && reason) return { runs: [], stalled: [{ shard, files: paths, reason,
|
|
1787
|
+
...(lastFile ? { lastFile } : {}), ...(placedOn ? { lastHost: placedOn } : {}),
|
|
1788
|
+
...(reason === 'unattributed' ? { summaryFailures: fails, namedFailures, ...(unattributedDetail ? { detail: unattributedDetail } : {}) } : {}) }] };
|
|
1585
1789
|
// A shared Job may finish some file parts before another part loses its JUnit/log/rc. Keep every complete part's
|
|
1586
1790
|
// original verdict, including a failure, and rerun only parts without the full artifact trio.
|
|
1587
1791
|
if (batchIsolation && !batchArtifactsComplete && batchArtifactDir && reason) {
|
|
@@ -1714,8 +1918,43 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1714
1918
|
const rejected = settled.find((result): result is PromiseRejectedResult => result.status === 'rejected');
|
|
1715
1919
|
if (rejected) throw rejected.reason;
|
|
1716
1920
|
const results = settled.flatMap((result) => result.status === 'fulfilled' ? [result.value] : []);
|
|
1717
|
-
|
|
1921
|
+
let stalledShards = results.flatMap((result) => result.stalled);
|
|
1718
1922
|
const measured = results.flatMap((result) => result.runs);
|
|
1923
|
+
// GATE-STALLED-TAIL-REMEASURE: after the sweep, give only a one-file no-output leaf one fresh attempt on a different
|
|
1924
|
+
// pool member. Keep the original unmeasured evidence if no alternate is available or the remeasure is incomplete.
|
|
1925
|
+
const remainingStalls: typeof stalledShards = [];
|
|
1926
|
+
for (const stalled of stalledShards) {
|
|
1927
|
+
const alternatives = stalled.lastHost
|
|
1928
|
+
? poolScheduler.members.filter((member) => member.context !== stalled.lastHost && !downMembers.has(member.context))
|
|
1929
|
+
: [];
|
|
1930
|
+
if (!stalled.stall || stalled.reason !== 'no-output' || stalled.files.length !== 1 || !stalled.lastHost || !alternatives.length) {
|
|
1931
|
+
remainingStalls.push(stalled);
|
|
1932
|
+
continue;
|
|
1933
|
+
}
|
|
1934
|
+
debug.log('release-loop.gate', 'pod-stalled-tail-remeasure-start', { shard: stalled.shard, file: stalled.files[0], fromHost: stalled.lastHost,
|
|
1935
|
+
availableHosts: alternatives.map((member) => member.context) });
|
|
1936
|
+
try {
|
|
1937
|
+
tailPlacedHost = undefined;
|
|
1938
|
+
const tail = await runShard(stalled.files, stalled.shard, 0, '-tail', true, true, undefined, 0, false, new Set([stalled.lastHost]), true);
|
|
1939
|
+
if (tail.stalled.length) {
|
|
1940
|
+
const unresolved = tail.stalled[0]!;
|
|
1941
|
+
remainingStalls.push({ ...stalled, tailRemeasure: { reason: unresolved.reason,
|
|
1942
|
+
...(unresolved.lastHost ?? tailPlacedHost ? { host: unresolved.lastHost ?? tailPlacedHost } : {}),
|
|
1943
|
+
...(unresolved.detail ? { detail: unresolved.detail } : {}) } });
|
|
1944
|
+
debug.log('release-loop.gate', 'pod-stalled-tail-remeasure-incomplete', { shard: stalled.shard, file: stalled.files[0],
|
|
1945
|
+
fromHost: stalled.lastHost, toHost: unresolved.lastHost ?? null, reason: unresolved.reason }, { level: 'warn' });
|
|
1946
|
+
} else {
|
|
1947
|
+
measured.push(...tail.runs);
|
|
1948
|
+
debug.log('release-loop.gate', 'pod-stalled-tail-remeasure-complete', { shard: stalled.shard, file: stalled.files[0],
|
|
1949
|
+
fromHost: stalled.lastHost, toHost: tailPlacedHost ?? null });
|
|
1950
|
+
}
|
|
1951
|
+
} catch (error) {
|
|
1952
|
+
remainingStalls.push({ ...stalled, tailRemeasure: { reason: 'error', ...(tailPlacedHost ? { host: tailPlacedHost } : {}), detail: String(error).slice(0, 500) } });
|
|
1953
|
+
debug.log('release-loop.gate', 'pod-stalled-tail-remeasure-error', { shard: stalled.shard, file: stalled.files[0],
|
|
1954
|
+
fromHost: stalled.lastHost, error: String(error).slice(0, 500) }, { level: 'warn' });
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
stalledShards = remainingStalls;
|
|
1719
1958
|
const total = measured.reduce((sum, run) => ({ pass: sum.pass + run.pass, fail: sum.fail + run.fail,
|
|
1720
1959
|
errors: sum.errors + run.errors, ran: sum.ran + run.ran, files: sum.files + run.files }),
|
|
1721
1960
|
{ pass: 0, fail: 0, errors: 0, ran: 0, files: 0 });
|
|
@@ -1733,6 +1972,21 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1733
1972
|
...(abortedShards ? { abortedShards } : {}) };
|
|
1734
1973
|
};
|
|
1735
1974
|
const runPodRecheckBatch = async (files: readonly string[], commit: string, pod: NonNullable<GateOptions['pod']>, timeoutMs: number, relaxed = false): Promise<Map<string, CommandResult>> => {
|
|
1975
|
+
const warmShadowScope = pod.warmShadowScope ?? { requested: false };
|
|
1976
|
+
const shadowTimeoutMs = pod.warmShadowTimeoutMs ?? 150_000;
|
|
1977
|
+
if (!Number.isSafeInteger(shadowTimeoutMs) || shadowTimeoutMs < 1 || shadowTimeoutMs > 150_000)
|
|
1978
|
+
throw new Error('invalid warm shadow timeout');
|
|
1979
|
+
let shadowDeadline = 0;
|
|
1980
|
+
const boundedShadow = async <T>(phase: string, operation: () => Promise<T>): Promise<T> => {
|
|
1981
|
+
const remainingMs = shadowDeadline - Date.now();
|
|
1982
|
+
if (remainingMs <= 0) throw new Error(`warm ${phase} timeout after ${shadowTimeoutMs}ms`);
|
|
1983
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
1984
|
+
try {
|
|
1985
|
+
return await Promise.race([Promise.resolve().then(operation), new Promise<never>((_resolve, reject) => {
|
|
1986
|
+
timer = setTimeout(() => reject(new Error(`warm ${phase} timeout after ${shadowTimeoutMs}ms`)), remainingMs);
|
|
1987
|
+
})]);
|
|
1988
|
+
} finally { if (timer) clearTimeout(timer); }
|
|
1989
|
+
};
|
|
1736
1990
|
if (!files.length || files.length > GATE_RECHECK_POD_BATCH_FILES || new Set(files).size !== files.length
|
|
1737
1991
|
|| files.some((file) => !/^(?:[\w.-]+\/)*[\w.-]+\.test\.[cm]?[jt]sx?$/.test(file)
|
|
1738
1992
|
|| file.split('/').some((part) => part === '.' || part === '..')))
|
|
@@ -1741,11 +1995,12 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1741
1995
|
const installSlots = pod.installSlots ?? POD_INSTALL_SLOTS_DEFAULT;
|
|
1742
1996
|
if (!Number.isSafeInteger(installSlots) || installSlots < 0) throw new Error('invalid pod recheck install slots');
|
|
1743
1997
|
const bunCache = pod.bunCache?.trim() || process.env[POD_BUN_CACHE_HOST_PATH]?.trim();
|
|
1998
|
+
const attemptId = randomUUID();
|
|
1744
1999
|
const partLimitSeconds = Math.ceil(timeoutMs / 1000);
|
|
1745
2000
|
const deadlineSeconds = partLimitSeconds * Math.ceil(files.length / 2) + POD_SHARD_EMIT_RESERVE_SECONDS + 60;
|
|
1746
2001
|
const shell = gateShardShell({ parts: files.map((file) => [file]), cdpPatterns: [], softSeconds: softDeadlineSeconds(deadlineSeconds),
|
|
1747
2002
|
cachePrefix: bunCache ? podBunCacheVolume(bunCache).shellPrefix : '', installSlots, parallelParts: files.length > 1 ? 2 : 1,
|
|
1748
|
-
partLimitSeconds, ...(relaxed ? { testTimeoutMs: GATE_TIMEOUT_RECHECK_MS } : {}) });
|
|
2003
|
+
partLimitSeconds, sourceBinding: { cutSha: commit, attemptId }, ...(relaxed ? { testTimeoutMs: GATE_TIMEOUT_RECHECK_MS } : {}) });
|
|
1749
2004
|
const job = await podCommand({ pool: pod.pool, ...(poolOverride ? { poolScheduler: poolOverride } : {}), clone: true,
|
|
1750
2005
|
source: { kind: 'commit', sha: commit }, deadlineSeconds, ...(bunCache ? { bunCache } : {}), ...gateHostMirror(pod),
|
|
1751
2006
|
...(installSlots > 0 ? { installSlots: POD_INSTALL_SLOTS_HOST_PATH } : {}), ...(pod.cpu ? { cpu: pod.cpu } : {}),
|
|
@@ -1755,8 +2010,13 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1755
2010
|
return existsSync(path) ? readFileSync(path, 'utf8') : undefined;
|
|
1756
2011
|
};
|
|
1757
2012
|
const installed = artifact('install.rc')?.trim() === '0';
|
|
2013
|
+
const warmEligible = files.length === 1 && pod.warmEligible === true && !!pod.warmReplay && !!pod.warmQuarantine && !!pod.warmFinalize
|
|
2014
|
+
&& pod.warmCutSha === commit;
|
|
2015
|
+
const incomplete = (message: string, file: string): Error => warmEligible
|
|
2016
|
+
? new WarmRecheckIncomplete(message, file, { files: [file], attemptId, status: 'unmeasured', reason: 'reference-unmeasured', quarantined: 'no' })
|
|
2017
|
+
: new Error(message);
|
|
1758
2018
|
if (!installed || job.exitCode !== 0)
|
|
1759
|
-
throw
|
|
2019
|
+
throw incomplete(`pod isolated recheck setup incomplete: ${commit.slice(0, 9)} job=${job.job} exit=${job.exitCode} install=${installed}`, files[0]!);
|
|
1760
2020
|
const timedOut = artifact('shard.timeout') !== undefined;
|
|
1761
2021
|
const results = new Map<string, CommandResult>();
|
|
1762
2022
|
for (const [index, file] of files.entries()) {
|
|
@@ -1765,11 +2025,105 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1765
2025
|
const rc = rcText && /^\d+$/.test(rcText) ? Number(rcText) : undefined;
|
|
1766
2026
|
const partTimedOut = artifact(`part-${index}.timeout`) !== undefined;
|
|
1767
2027
|
if (rc === undefined || output === undefined || partTimedOut) {
|
|
1768
|
-
if (!timedOut && !partTimedOut) throw
|
|
2028
|
+
if (!timedOut && !partTimedOut) throw incomplete(`pod isolated recheck incomplete: ${file} @${commit.slice(0, 9)} job=${job.job} rc=${rc ?? 'missing'}`, file);
|
|
1769
2029
|
results.set(file, { rc: 124, output: output ?? '', timedOut: true });
|
|
1770
|
-
} else
|
|
2030
|
+
} else {
|
|
2031
|
+
// The comparator only accepts a complete JUnit/log pair tied to this exact Job attempt.
|
|
2032
|
+
// A missing or damaged artifact leaves the existing Job verdict authoritative.
|
|
2033
|
+
let warmReference: WarmWorkerFileResult | null = null;
|
|
2034
|
+
if (warmEligible) {
|
|
2035
|
+
try {
|
|
2036
|
+
const prefix = join(job.artifactsDir, `recheck-${index}`);
|
|
2037
|
+
const part = (suffix: string) => join(job.artifactsDir, `part-${index}.${suffix}`);
|
|
2038
|
+
for (const suffix of ['log', 'rc', 'junit.xml']) if (existsSync(part(suffix))) copyFileSync(part(suffix), `${prefix}.${suffix}`);
|
|
2039
|
+
writeFileSync(`${prefix}.json`, JSON.stringify({ commit, attemptId, files: [file], rc,
|
|
2040
|
+
jobFailed: false, jobExitCode: job.exitCode }) + '\n', { mode: 0o600 });
|
|
2041
|
+
if (writeWarmJobManifest(prefix, file, commit, attemptId))
|
|
2042
|
+
warmReference = warmReferenceFromJobArtifacts(prefix, file, { cutSha: commit, attemptId });
|
|
2043
|
+
} catch (error) {
|
|
2044
|
+
debug.log('release-loop.gate', 'warm-reference-unmeasured', { file, attemptId, error: String(error) });
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
results.set(file, { rc, output, warmReference, warmAttemptId: attemptId });
|
|
2048
|
+
}
|
|
1771
2049
|
debug.log('release-loop.gate', 'pod-isolated-recheck', { file, commit, job: job.job, rc: rc ?? 124, relaxed });
|
|
1772
2050
|
}
|
|
2051
|
+
if (warmEligible && pod.warmReplay && pod.warmQuarantine && pod.warmFinalize) {
|
|
2052
|
+
shadowDeadline = Date.now() + shadowTimeoutMs;
|
|
2053
|
+
const first = results.get(files[0]!)!;
|
|
2054
|
+
const reference = first.warmReference;
|
|
2055
|
+
if (!reference) {
|
|
2056
|
+
first.warmShadow = { files: [...files], attemptId, status: 'unmeasured', reason: 'reference-unmeasured', quarantined: 'no' };
|
|
2057
|
+
debug.log('release-loop.gate', 'warm-shadow-recheck', first.warmShadow);
|
|
2058
|
+
} else if (pod.warmReplayOwner !== false && (warmShadowScope.attempts ?? (warmShadowScope.requested ? 1 : 0)) < WARM_REPLAY_BUDGET) {
|
|
2059
|
+
warmShadowScope.attempts = (warmShadowScope.attempts ?? (warmShadowScope.requested ? 1 : 0)) + 1;
|
|
2060
|
+
warmShadowScope.requested = true;
|
|
2061
|
+
let observation: WarmShadowObservation;
|
|
2062
|
+
try {
|
|
2063
|
+
const request = { protocol: 1 as const, runId: randomUUID(), attemptId, cutSha: commit,
|
|
2064
|
+
...(pod.warmBaselineSha ? { baselineSha: pod.warmBaselineSha } : {}),
|
|
2065
|
+
deadlineAt: Math.min(Date.now() + timeoutMs * files.length + 60_000, shadowDeadline - 1_000),
|
|
2066
|
+
files, envProfile: 'gate-v1' };
|
|
2067
|
+
// The producer honors deadlineAt. An injected/slow producer may still settle after our wait cap:
|
|
2068
|
+
// fence any late lease before it can be mistaken for a reusable worker.
|
|
2069
|
+
let replayTimedOut = false;
|
|
2070
|
+
const pendingReplay = Promise.resolve().then(() => pod.warmReplay!(request));
|
|
2071
|
+
void pendingReplay.then((late) => {
|
|
2072
|
+
if (replayTimedOut && late.command) void Promise.resolve().then(() => pod.warmQuarantine!(late.command!, 'late-replay-after-timeout'))
|
|
2073
|
+
.catch((error) => debug.log('release-loop.gate', 'warm-shadow-late-quarantine-failed', { attemptId, error: String(error) }));
|
|
2074
|
+
}, (error) => {
|
|
2075
|
+
if (replayTimedOut) debug.log('release-loop.gate', 'warm-shadow-late-replay-failed', { attemptId, error: String(error) });
|
|
2076
|
+
});
|
|
2077
|
+
let replay: WarmReplayResult;
|
|
2078
|
+
try { replay = await boundedShadow('replay', () => pendingReplay); }
|
|
2079
|
+
catch (error) { replayTimedOut = true; throw error; }
|
|
2080
|
+
if (!replay.command) {
|
|
2081
|
+
observation = { files: [...files], attemptId, status: 'fallback', reason: replay.transport.reason, quarantined: 'no' };
|
|
2082
|
+
} else if (replay.transport.status !== 'observed') {
|
|
2083
|
+
let quarantined: WarmShadowObservation['quarantined'] = 'yes';
|
|
2084
|
+
try { await boundedShadow('quarantine', () => pod.warmQuarantine!(replay.command!, `transport-${replay.transport.reason}`)); }
|
|
2085
|
+
catch (error) {
|
|
2086
|
+
quarantined = 'failed';
|
|
2087
|
+
debug.log('release-loop.gate', 'warm-shadow-quarantine-failed', { attemptId,
|
|
2088
|
+
reason: replay.transport.reason, error: String(error) });
|
|
2089
|
+
}
|
|
2090
|
+
observation = { files: [...files], attemptId, status: 'fallback', reason: replay.transport.reason, quarantined };
|
|
2091
|
+
} else {
|
|
2092
|
+
const bound = warmCommandMatchesRequest(replay.command, request);
|
|
2093
|
+
const decision = bound ? assessWarmWorkerShadow(replay.command, [reference], replay.transport.reply)
|
|
2094
|
+
: { reason: 'identity' as const, quarantineWorker: true };
|
|
2095
|
+
let reason: string = decision.reason;
|
|
2096
|
+
let status: WarmShadowObservation['status'] = 'observed';
|
|
2097
|
+
let quarantined: WarmShadowObservation['quarantined'] = 'no';
|
|
2098
|
+
let finalized = false;
|
|
2099
|
+
if (decision.reason === 'match') {
|
|
2100
|
+
try { await boundedShadow('finalize', () => pod.warmFinalize!(replay.command!)); finalized = true; }
|
|
2101
|
+
catch (error) {
|
|
2102
|
+
reason = 'finalize-failed';
|
|
2103
|
+
status = 'fallback';
|
|
2104
|
+
debug.log('release-loop.gate', 'warm-shadow-finalize-failed', { attemptId, error: String(error) });
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
if (!finalized) {
|
|
2108
|
+
try { await boundedShadow('quarantine', () => pod.warmQuarantine!(replay.command!, reason)); quarantined = 'yes'; }
|
|
2109
|
+
catch (error) {
|
|
2110
|
+
quarantined = 'failed';
|
|
2111
|
+
debug.log('release-loop.gate', 'warm-shadow-quarantine-failed', { attemptId, reason, error: String(error) });
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
observation = { files: [...files], attemptId, status, reason, quarantined, finalized,
|
|
2115
|
+
cutSha: replay.command.cutSha, baselineSha: replay.command.baselineSha, workerKey: replay.command.key };
|
|
2116
|
+
}
|
|
2117
|
+
} catch (error) {
|
|
2118
|
+
observation = { files: [...files], attemptId, status: 'unmeasured', reason: String(error), quarantined: 'unavailable' };
|
|
2119
|
+
}
|
|
2120
|
+
first.warmShadow = observation;
|
|
2121
|
+
debug.log('release-loop.gate', 'warm-shadow-recheck', observation);
|
|
2122
|
+
} else {
|
|
2123
|
+
first.warmShadow = { files: [...files], attemptId, status: 'fallback', reason: 'replay-budget-exhausted', quarantined: 'no' };
|
|
2124
|
+
debug.log('release-loop.gate', 'warm-shadow-recheck', first.warmShadow);
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
1773
2127
|
return results;
|
|
1774
2128
|
};
|
|
1775
2129
|
return {
|
|
@@ -1943,6 +2297,8 @@ export function saveBaselineFileCache(ledger: string, commit: string, rows: Base
|
|
|
1943
2297
|
|
|
1944
2298
|
export async function judgeGate(opts: GateOptions, runner: GateRunner = createGateRunner(opts.repo ?? process.cwd(), opts.remote)): Promise<GateResult> {
|
|
1945
2299
|
const start = Date.now();
|
|
2300
|
+
if (opts.pod?.warmReplay) opts = { ...opts, pod: { ...opts.pod,
|
|
2301
|
+
warmShadowScope: { requested: false, attempts: 0 } } };
|
|
1946
2302
|
const explicitConfig = getElanousConfigDirOverride();
|
|
1947
2303
|
const root = explicitConfig ? effectiveInstanceRoot() : (opts.instanceRoot ?? effectiveInstanceRoot());
|
|
1948
2304
|
// The freeze is read from the same root the gate runs in (a config-dir override must not move it elsewhere).
|
|
@@ -1969,17 +2325,26 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
1969
2325
|
};
|
|
1970
2326
|
const repo = resolve(opts.repo ?? process.cwd());
|
|
1971
2327
|
const result: GateResult = { outcome: 'error', commit: opts.commit, introduced: [], preexisting: 0, fixed: 0, knownEnv: 0, knownEnvCleared: [], stalledEnv: [], rcNonzeroNoFailures: [], durationMs: 0 };
|
|
2328
|
+
let baselineCommitForVerdict = opts.baselineCommit ?? '';
|
|
1972
2329
|
const preexistingIds: string[] = [];
|
|
1973
2330
|
let knownEnvIds: string[] = [];
|
|
1974
2331
|
let work: string | undefined;
|
|
1975
2332
|
let baseSnapshot = false;
|
|
1976
2333
|
let cutFailures: SweepFailures | undefined;
|
|
2334
|
+
let cutSweepUsable = false;
|
|
2335
|
+
let cutSweepRc: number | null = null;
|
|
2336
|
+
let radiusShadow: GateRadiusShadowPlan | undefined;
|
|
1977
2337
|
const trees: string[] = [];
|
|
1978
2338
|
try {
|
|
1979
2339
|
if (!sha.test(opts.commit) || !versionPattern.test(opts.version)
|
|
1980
2340
|
|| (opts.baselineVersion && !versionPattern.test(opts.baselineVersion))
|
|
1981
2341
|
|| (opts.baselineCommit && !sha.test(opts.baselineCommit))) throw new Error('invalid commit or version');
|
|
1982
2342
|
if (!opts.baselineVersion) throw new Error('previous release version required (--baseline-version)');
|
|
2343
|
+
// Observation only. Failure to plan cannot change the gate selector or verdict.
|
|
2344
|
+
try { radiusShadow = planGateRadiusShadow({ repo, ledgerRoot: ledger, version: opts.version,
|
|
2345
|
+
baselineVersion: opts.baselineVersion, forCommit: opts.commit, instanceRoot: root,
|
|
2346
|
+
expectedBaselineCommit: opts.baselineCommit }); }
|
|
2347
|
+
catch (error) { debug.log('release-loop.gate', 'radius-shadow-unavailable', { reason: String(error) }); }
|
|
1983
2348
|
if (!Number.isSafeInteger(opts.recheckFileLimit ?? GATE_HOST_RECHECK_FILE_LIMIT) || (opts.recheckFileLimit ?? GATE_HOST_RECHECK_FILE_LIMIT) < 1)
|
|
1984
2349
|
throw new Error('invalid gate recheck file limit');
|
|
1985
2350
|
if (opts.pod && (!opts.pod.pool || !Number.isSafeInteger(opts.pod.shards ?? 24) || (opts.pod.shards ?? 24) < 1
|
|
@@ -2061,6 +2426,9 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
2061
2426
|
// --ledger-root (tests) keeps it in that ledger.
|
|
2062
2427
|
shardsFile: { path: opts.ledgerRoot ? gateShardsPath(opts.version, opts.ledgerRoot) : gateShardsPath(opts.version), version: opts.version } } : undefined,
|
|
2063
2428
|
partial?.files);
|
|
2429
|
+
cutSweepRc = cutRun?.rc ?? null;
|
|
2430
|
+
cutSweepUsable = !!cutRun && (cutRun.rc === 0 || cutRun.rc === 1) && !cutRun.timedOut
|
|
2431
|
+
&& !cutRun.abortedShards && !cutRun.stalledEnv?.length;
|
|
2064
2432
|
if (cutRun?.abortedShards) result.abortedShards = cutRun.abortedShards;
|
|
2065
2433
|
const localStalled = async (run: CommandResult, tree: string, commit: string, label: string) => {
|
|
2066
2434
|
const stalled = run.stalledEnv ?? [];
|
|
@@ -2145,6 +2513,7 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
2145
2513
|
if (partial) result.partial = { priorCommit: partial.priorCommit, files: partial.files.length };
|
|
2146
2514
|
cutFailures = cut;
|
|
2147
2515
|
const baseSha = opts.baselineCommit ?? baselineReleaseCommit(opts.baselineVersion);
|
|
2516
|
+
baselineCommitForVerdict = baseSha;
|
|
2148
2517
|
const cached = cachedBaseline(opts.baselineVersion, baseSha);
|
|
2149
2518
|
const recorded = cached ? undefined : baselineFromCutLogs(join(ledger, 'release', opts.baselineVersion, 'gate-logs', 'cut'), baseSha);
|
|
2150
2519
|
const trusted = cached?.saved ?? (recorded?.complete ? recorded : undefined);
|
|
@@ -2221,6 +2590,29 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
2221
2590
|
catch (error) { debug.log('release.gate', 'baseline-cache-write-failed', { commit: row.commit, file: row.file, error: String(error) }); }
|
|
2222
2591
|
};
|
|
2223
2592
|
const recheckFiles = [...newFailureFiles].sort();
|
|
2593
|
+
const eligible = recheckFiles.filter((file) => !cutHostFailed.has(file) && !prefetchedCut.has(file));
|
|
2594
|
+
const warmEligibleFiles = new Set(opts.pod?.warmReplay && opts.pod.warmQuarantine && opts.pod.warmFinalize
|
|
2595
|
+
? eligible.slice(0, WARM_REPLAY_BUDGET) : []);
|
|
2596
|
+
// The source-bound lock comes from the cut commit itself, never from the producer's claim.
|
|
2597
|
+
const history = readWarmVerdictHistory(verdictFile(ledger, opts.version));
|
|
2598
|
+
let warmLockHash = '';
|
|
2599
|
+
if (opts.pod?.warmReuseConfig?.gateRecheck && history?.length) {
|
|
2600
|
+
try {
|
|
2601
|
+
const lock = await runner.localCommand('git', ['show', `${opts.commit}:bun.lock`], repo);
|
|
2602
|
+
if (lock.rc === 0 && lock.output) warmLockHash = createHash('sha256').update(lock.output).digest('hex');
|
|
2603
|
+
} catch { /* Unmeasured evidence keeps reuse off. */ }
|
|
2604
|
+
}
|
|
2605
|
+
const warmEvidence = warmEvidenceFromVerdicts(history, opts.version, opts.commit, baseSha, warmLockHash);
|
|
2606
|
+
const warmDecision = decideWarmReuse('gate-recheck', opts.pod?.warmReuseConfig,
|
|
2607
|
+
warmEvidence ? { 'gate-recheck': warmEvidence } : undefined);
|
|
2608
|
+
const verifiedWorkerKey = history?.at(-1)?.warmShadow.at(-1)?.workerKey;
|
|
2609
|
+
if (warmDecision.live && verifiedWorkerKey && opts.pod && runner.recheckPod) {
|
|
2610
|
+
// Only the first two files may adopt; replay them serially before scheduling fresh fallbacks.
|
|
2611
|
+
for (const file of eligible.slice(0, 2)) {
|
|
2612
|
+
const adopted = await tryWarmGateAdoption(file, opts.commit, baseSha, verifiedWorkerKey, opts.pod);
|
|
2613
|
+
if (adopted) prefetchedCut.set(file, adopted);
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2224
2616
|
const recheckLimit = opts.recheckFileLimit ?? GATE_HOST_RECHECK_FILE_LIMIT;
|
|
2225
2617
|
const recheckConcurrency = opts.pod?.isolationConcurrency ?? 4;
|
|
2226
2618
|
if (opts.pod && (!Number.isSafeInteger(recheckConcurrency) || recheckConcurrency < 1)) throw new Error('invalid pod recheck concurrency');
|
|
@@ -2230,13 +2622,19 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
2230
2622
|
concurrency: opts.pod && runner.recheckPod ? recheckConcurrency : 1, executor: opts.pod && runner.recheckPod ? 'pod' : 'host',
|
|
2231
2623
|
remote: opts.remote ?? null, podBatchFiles: opts.pod && runner.recheckPod && runner.recheckPodBatch ? GATE_RECHECK_POD_BATCH_FILES : 1 });
|
|
2232
2624
|
const recheckStartedAt = Date.now();
|
|
2625
|
+
const recordWarmShadow = (shadow?: WarmShadowObservation) => {
|
|
2626
|
+
if (shadow && !result.warmShadow?.some((entry) => entry.attemptId === shadow.attemptId))
|
|
2627
|
+
(result.warmShadow ??= []).push(shadow);
|
|
2628
|
+
};
|
|
2233
2629
|
const recheckFile = async (file: string, index: number) => {
|
|
2234
2630
|
debug.log('release-loop.gate', 'isolated-recheck-progress',
|
|
2235
2631
|
{ index: index + 1, total: recheckFiles.length, file, elapsedSec: Math.round((Date.now() - recheckStartedAt) / 1000) });
|
|
2236
2632
|
// 0.2.15: the cut re-check of a new-failure file (InsidePage.test.tsx · headless Chrome) hung for good — limit it like the host re-run.
|
|
2237
2633
|
const cutCheck = cutHostFailed.has(file) ? undefined : prefetchedCut.get(file) ?? (opts.pod && runner.recheckPod
|
|
2238
|
-
|
|
2634
|
+
? await runner.recheckPod(file, opts.commit, { ...opts.pod, warmCutSha: opts.commit, warmBaselineSha: baseSha,
|
|
2635
|
+
warmEligible: warmEligibleFiles.has(file) }, GATE_ISOLATED_TIMEOUT_MS)
|
|
2239
2636
|
: await runner.command('bun', ['run', 'test:deterministic', asPath(file)], cutTree, GATE_ISOLATED_TIMEOUT_MS));
|
|
2637
|
+
recordWarmShadow(cutCheck?.warmShadow);
|
|
2240
2638
|
if (cutCheck?.timedOut) {
|
|
2241
2639
|
debug.log('release-loop.gate', 'isolated-timeout', { file, label: 'cut isolated', limitMs: GATE_ISOLATED_TIMEOUT_MS });
|
|
2242
2640
|
result.stalledEnv.push({ file, reason: 'isolated-timeout', local: 'timeout' });
|
|
@@ -2258,8 +2656,10 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
2258
2656
|
const shaped = candidates.filter((id) => timedOut.has(id) && isolatedCut.failures.includes(id));
|
|
2259
2657
|
if (shaped.length) {
|
|
2260
2658
|
const relaxed = opts.pod && runner.recheckPod
|
|
2261
|
-
? await runner.recheckPod(file, opts.commit, opts.pod,
|
|
2659
|
+
? await runner.recheckPod(file, opts.commit, { ...opts.pod, warmCutSha: opts.commit, warmBaselineSha: baseSha,
|
|
2660
|
+
warmEligible: warmEligibleFiles.has(file) }, GATE_ISOLATED_TIMEOUT_MS, true)
|
|
2262
2661
|
: await runner.command('bun', ['run', 'test:deterministic', asPath(file), '--timeout', String(GATE_TIMEOUT_RECHECK_MS)], cutTree, GATE_ISOLATED_TIMEOUT_MS);
|
|
2662
|
+
recordWarmShadow(relaxed.warmShadow);
|
|
2263
2663
|
let flaky: string[] = [];
|
|
2264
2664
|
let recheck: 'read' | 'timeout' | 'unreadable' = 'read';
|
|
2265
2665
|
if (relaxed.timedOut) recheck = 'timeout';
|
|
@@ -2329,20 +2729,51 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
2329
2729
|
const cap = opts.pod && runner.recheckPod ? recheckConcurrency : 1;
|
|
2330
2730
|
const settleStarted = async <T>(jobs: Promise<T>[]): Promise<T[]> => {
|
|
2331
2731
|
const settled = await Promise.allSettled(jobs);
|
|
2332
|
-
const rejected = settled.
|
|
2333
|
-
if (rejected) throw rejected.reason;
|
|
2732
|
+
const rejected = settled.filter((item): item is PromiseRejectedResult => item.status === 'rejected');
|
|
2733
|
+
if (rejected.length) throw new SettledRecheckFailures(rejected.map((item) => item.reason));
|
|
2334
2734
|
return settled.map((item) => (item as PromiseFulfilledResult<T>).value);
|
|
2335
2735
|
};
|
|
2736
|
+
// Always attempt both eligible references before a failed Job can end the verdict. A cap of one
|
|
2737
|
+
// still runs the second Job, and successes retain their shadow if a peer throws.
|
|
2738
|
+
if (warmEligibleFiles.size && opts.pod && runner.recheckPod) {
|
|
2739
|
+
const completed = new Map<string, CommandResult>();
|
|
2740
|
+
const failures: unknown[] = [];
|
|
2741
|
+
const warmFiles = [...warmEligibleFiles].filter((file) => !prefetchedCut.has(file));
|
|
2742
|
+
for (let start = 0; start < warmFiles.length; start += cap) {
|
|
2743
|
+
const settled = await Promise.allSettled(warmFiles.slice(start, start + cap).map(async (file) => {
|
|
2744
|
+
const pod = { ...opts.pod!, warmCutSha: opts.commit, warmBaselineSha: baseSha, warmEligible: true,
|
|
2745
|
+
warmReplayOwner: true };
|
|
2746
|
+
return runner.recheckPodBatch
|
|
2747
|
+
? (await runner.recheckPodBatch([file], opts.commit, pod, GATE_ISOLATED_TIMEOUT_MS)).get(file)
|
|
2748
|
+
: await runner.recheckPod!(file, opts.commit, pod, GATE_ISOLATED_TIMEOUT_MS);
|
|
2749
|
+
}));
|
|
2750
|
+
for (const [index, item] of settled.entries()) {
|
|
2751
|
+
if (item.status === 'rejected') failures.push(item.reason);
|
|
2752
|
+
else if (item.value) completed.set(warmFiles[start + index]!, item.value);
|
|
2753
|
+
else failures.push(new Error(`pod isolated recheck missing result: ${warmFiles[start + index]}`));
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
for (const [file, run] of completed) {
|
|
2757
|
+
recordWarmShadow(run.warmShadow);
|
|
2758
|
+
prefetchedCut.set(file, run);
|
|
2759
|
+
}
|
|
2760
|
+
if (failures.length) throw new SettledRecheckFailures(failures);
|
|
2761
|
+
}
|
|
2336
2762
|
for (let start = 0; start < recheckFiles.length; start += recheckLimit) {
|
|
2337
2763
|
const window = recheckFiles.slice(start, start + recheckLimit);
|
|
2338
2764
|
debug.log('release-loop.gate', 'isolated-recheck-window', { start, files: window.length, total: recheckFiles.length });
|
|
2339
2765
|
if (opts.pod && runner.recheckPod && runner.recheckPodBatch) {
|
|
2340
2766
|
const pending = window.filter((file) => !cutHostFailed.has(file) && !prefetchedCut.has(file));
|
|
2341
|
-
const
|
|
2342
|
-
|
|
2767
|
+
const warmSingles = pending.filter((file) => warmEligibleFiles.has(file)).length;
|
|
2768
|
+
const groups = [
|
|
2769
|
+
...pending.slice(0, warmSingles).map((file) => [file]),
|
|
2770
|
+
...Array.from({ length: Math.ceil((pending.length - warmSingles) / GATE_RECHECK_POD_BATCH_FILES) }, (_, index) =>
|
|
2771
|
+
pending.slice(warmSingles + index * GATE_RECHECK_POD_BATCH_FILES, warmSingles + (index + 1) * GATE_RECHECK_POD_BATCH_FILES)),
|
|
2772
|
+
];
|
|
2343
2773
|
for (let next = 0; next < groups.length; next += cap) {
|
|
2344
2774
|
const batches = await settleStarted(groups.slice(next, next + cap).map((files) =>
|
|
2345
|
-
runner.recheckPodBatch!(files, opts.commit, opts.pod!,
|
|
2775
|
+
runner.recheckPodBatch!(files, opts.commit, { ...opts.pod!, warmCutSha: opts.commit, warmBaselineSha: baseSha,
|
|
2776
|
+
warmEligible: files.length === 1 && warmEligibleFiles.has(files[0]!) }, GATE_ISOLATED_TIMEOUT_MS)));
|
|
2346
2777
|
for (const batch of batches) for (const [file, run] of batch) prefetchedCut.set(file, run);
|
|
2347
2778
|
}
|
|
2348
2779
|
}
|
|
@@ -2375,6 +2806,13 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
2375
2806
|
}
|
|
2376
2807
|
} catch (error) {
|
|
2377
2808
|
result.error = error instanceof Error ? error.message : String(error);
|
|
2809
|
+
const warmFailures = (error instanceof SettledRecheckFailures ? error.failures : [error])
|
|
2810
|
+
.filter((failure): failure is WarmRecheckIncomplete => failure instanceof WarmRecheckIncomplete);
|
|
2811
|
+
if (warmFailures.length) {
|
|
2812
|
+
(result.warmShadow ??= []).push(...warmFailures.map((failure) => failure.shadow));
|
|
2813
|
+
result.unmeasuredRecheckFiles = [...new Set([...(result.unmeasuredRecheckFiles ?? []),
|
|
2814
|
+
...warmFailures.map((failure) => failure.file)])].sort();
|
|
2815
|
+
}
|
|
2378
2816
|
if (error instanceof StalledPodShards) {
|
|
2379
2817
|
result.stalledShards = error.stalledShards;
|
|
2380
2818
|
result.partialSummary = error.partialSummary;
|
|
@@ -2416,6 +2854,7 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
2416
2854
|
}
|
|
2417
2855
|
}
|
|
2418
2856
|
{
|
|
2857
|
+
const baselineCommit = baselineCommitForVerdict;
|
|
2419
2858
|
const ids = {
|
|
2420
2859
|
introduced: [...result.introduced].sort(),
|
|
2421
2860
|
knownEnv: [...knownEnvIds].sort(),
|
|
@@ -2425,7 +2864,8 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
2425
2864
|
...(result.unmeasuredErrors?.length ? { unmeasuredErrors: result.unmeasuredErrors } : {}),
|
|
2426
2865
|
...(result.interferenceErrors?.length ? { interferenceErrors: [...result.interferenceErrors].sort() } : {}),
|
|
2427
2866
|
};
|
|
2428
|
-
const observed = result.selectionMs !== undefined ? { selectionMs: result.selectionMs } : {}
|
|
2867
|
+
const observed = { ...(result.selectionMs !== undefined ? { selectionMs: result.selectionMs } : {}),
|
|
2868
|
+
...(result.warmShadow?.length ? { warmShadow: result.warmShadow } : {}) };
|
|
2429
2869
|
try {
|
|
2430
2870
|
for (const location of roots) {
|
|
2431
2871
|
const path = verdictFile(location, opts.version);
|
|
@@ -2436,7 +2876,10 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
2436
2876
|
mkdirSync(dirname(path), { recursive: true });
|
|
2437
2877
|
const temp = join(dirname(path), `.gate-verdict-${process.pid}-${randomUUID()}.tmp`);
|
|
2438
2878
|
try {
|
|
2439
|
-
|
|
2879
|
+
const warmHistory = readWarmVerdictHistory(path) ?? [];
|
|
2880
|
+
writeFileSync(temp, JSON.stringify({ version: opts.version, commit: opts.commit,
|
|
2881
|
+
outcome: result.outcome, ...ids, ...observed,
|
|
2882
|
+
...(opts.pod?.warmReplay ? { baselineCommit, warmHistory } : {}) }, null, 2) + '\n', { mode: 0o600 });
|
|
2440
2883
|
chmodSync(temp, 0o600);
|
|
2441
2884
|
renameSync(temp, path);
|
|
2442
2885
|
} finally { if (existsSync(temp)) rmSync(temp); }
|
|
@@ -2449,7 +2892,10 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
2449
2892
|
mkdirSync(dirname(path), { recursive: true });
|
|
2450
2893
|
const temp = join(dirname(path), `.gate-verdict-${process.pid}-${randomUUID()}.tmp`);
|
|
2451
2894
|
try {
|
|
2452
|
-
|
|
2895
|
+
const warmHistory = readWarmVerdictHistory(path) ?? [];
|
|
2896
|
+
writeFileSync(temp, JSON.stringify({ version: opts.version, commit: opts.commit,
|
|
2897
|
+
outcome: result.outcome, ...ids, ...observed,
|
|
2898
|
+
...(opts.pod?.warmReplay ? { baselineCommit, warmHistory } : {}) }, null, 2) + '\n', { mode: 0o600 });
|
|
2453
2899
|
chmodSync(temp, 0o600);
|
|
2454
2900
|
renameSync(temp, path);
|
|
2455
2901
|
} finally { if (existsSync(temp)) rmSync(temp); }
|
|
@@ -2465,6 +2911,20 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
2465
2911
|
...(result.outcome === 'error' ? { error: result.error } : {}),
|
|
2466
2912
|
});
|
|
2467
2913
|
}
|
|
2914
|
+
if (radiusShadow) {
|
|
2915
|
+
try {
|
|
2916
|
+
const record = { plan: radiusShadow, recordedAt: new Date().toISOString(),
|
|
2917
|
+
actual: shadowActual({ plan: radiusShadow, outcome: result.outcome,
|
|
2918
|
+
introduced: result.introduced, cutFailures: cutFailures?.failures ?? [], cutErrors: cutFailures?.errors ?? [], cutRc: cutSweepRc,
|
|
2919
|
+
fullSweep: cutSweepUsable && !result.partial && !!cutFailures && result.outcome !== 'error'
|
|
2920
|
+
&& !result.rcNonzeroNoFailures.length && !result.stalledShards?.length
|
|
2921
|
+
&& !result.stalledEnv.length && !result.unmeasuredRecheckFiles?.length
|
|
2922
|
+
&& !result.unmeasuredErrors?.length }) };
|
|
2923
|
+
const path = writeGateRadiusShadow(ledger, record);
|
|
2924
|
+
debug.log('release-loop.gate', 'radius-shadow-recorded', { path, proposed: radiusShadow.proposed,
|
|
2925
|
+
missedIntroduced: record.actual.missedIntroduced.length });
|
|
2926
|
+
} catch (error) { debug.log('release-loop.gate', 'radius-shadow-unavailable', { reason: String(error) }); }
|
|
2927
|
+
}
|
|
2468
2928
|
// GATE-LIVE-OBS-RC-WORDING ⓒ — the shards board says «판정 전» until this lands; then «새 실패 N» only (UX · gate-shards.ts).
|
|
2469
2929
|
// Same ledger as the board's own writes (sweep's shardsFile). A write failure is logged and never changes the verdict.
|
|
2470
2930
|
if (result.outcome !== 'error' && opts.pod) {
|
|
@@ -2606,13 +3066,20 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
|
|
|
2606
3066
|
|
|
2607
3067
|
/** The node's run: judge, emit the graph result line, and return the process exit code (ok 0 · regression 1 · else 2).
|
|
2608
3068
|
* `runner` is a seam for tests; the process entry passes none. */
|
|
2609
|
-
export async function runGateNode(argv: string[], env: NodeJS.ProcessEnv, runner?: GateRunner
|
|
3069
|
+
export async function runGateNode(argv: string[], env: NodeJS.ProcessEnv, runner?: GateRunner,
|
|
3070
|
+
judge: (opts: GateOptions, runner?: GateRunner) => Promise<GateResult> = judgeGate): Promise<number> {
|
|
2610
3071
|
const start = Date.now();
|
|
2611
3072
|
let result: GateResult;
|
|
2612
3073
|
let version = '';
|
|
2613
3074
|
let placement: { executor: string | null; host: string | null } | undefined;
|
|
2614
3075
|
try {
|
|
2615
|
-
const
|
|
3076
|
+
const parsed = parseOptions(argv, env);
|
|
3077
|
+
let config: unknown = null;
|
|
3078
|
+
if (parsed !== 'help' && !runner && parsed.pod) {
|
|
3079
|
+
try { config = getUserConfig(); }
|
|
3080
|
+
catch (error) { debug.log('release-loop.gate', 'warm-profile-unavailable', { error: String(error) }); }
|
|
3081
|
+
}
|
|
3082
|
+
const opts = parsed === 'help' ? parsed : withWarmGateHooks(parsed, config, !!runner);
|
|
2616
3083
|
if (opts !== 'help') {
|
|
2617
3084
|
placement = gateExecutorOf(opts);
|
|
2618
3085
|
debug.log('release-loop.gate', 'executor', { version: opts.version, ...placement, requested: opts.executor ?? null });
|
|
@@ -2631,7 +3098,7 @@ export async function runGateNode(argv: string[], env: NodeJS.ProcessEnv, runner
|
|
|
2631
3098
|
throw new Error('release gate host reservation requires graph runId and pinned state root');
|
|
2632
3099
|
const releaseHosts = context && hosts.length
|
|
2633
3100
|
? reserveReleaseGateHosts(reservationRoot!, context.runId!, hosts) : () => undefined;
|
|
2634
|
-
try { result = await (
|
|
3101
|
+
try { result = await judge(opts, runner); }
|
|
2635
3102
|
finally { releaseHosts(); }
|
|
2636
3103
|
} catch (error) {
|
|
2637
3104
|
result = { outcome: 'error', commit: '', introduced: [], preexisting: 0, fixed: 0, knownEnv: 0, knownEnvCleared: [], stalledEnv: [], rcNonzeroNoFailures: [], durationMs: Date.now() - start, error: String(error) };
|
|
@@ -2647,5 +3114,5 @@ if (import.meta.main) {
|
|
|
2647
3114
|
// The graph spawns this file as its own process, which inherits no daemon sink — without this every
|
|
2648
3115
|
// `release-loop.gate`/`release.gate`/`pod.pool` event stayed in the file trail and `elanous logs` read 0 rows.
|
|
2649
3116
|
await registerStandaloneLogSink('release-loop');
|
|
2650
|
-
process.exitCode = await runGateNode(process.argv.slice(2), process.env);
|
|
3117
|
+
process.exitCode = await runWithReleaseGateTraffic(process.env, () => runGateNode(process.argv.slice(2), process.env));
|
|
2651
3118
|
}
|