elanous 0.2.22 → 0.2.24
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/NOTICE +7 -0
- package/README.md +32 -22
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/{aBi6EyMtLsrl7J8eH4kF7 → Q-7iofoxOEv1WX18S35of}/_buildManifest.js +1 -1
- package/apps/pwa/out/_next/static/chunks/{119fc678.6f591cdb84ca5791.js → 0673cc54.8e2ef1c23e49e190.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{a9f2b54d-40c16bd0efdb04b8.js → 0a09e062-34f747a17d8d8c2e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{f8626309.a4e5d362b1d55e59.js → 0e3acd34.d42b2ef87fdf4234.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1168.a0fb3d9db76c00bd.js +77 -0
- package/apps/pwa/out/_next/static/chunks/{7175-350f4ce2873bc038.js → 1325-dc3f3c21eb181cb4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1357.a843c41d2cd7ec31.js +34 -0
- package/apps/pwa/out/_next/static/chunks/1430.53f49882b17607c2.js +24 -0
- package/apps/pwa/out/_next/static/chunks/1459-be6509b3419d8a9f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6767.8e6c5f6e744fcd9f.js → 1505.5826671109111e75.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/153.0a4e3cd516930491.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1572.7bc291352e241a45.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1593-d07ad068c120dfb0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1608.b7c900baa56cab32.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1615-4d8156febaac4b35.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1646.e03f0fbc26bc72f6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7327-2d69fb321ba73389.js → 1669-da843ed73aa301f5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/176.13ba0659e3e96fd4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1990.9c60f495ef33c070.js +36 -0
- package/apps/pwa/out/_next/static/chunks/2042e469.f86158dd5c1e51a1.js +122 -0
- package/apps/pwa/out/_next/static/chunks/2528.0af5c0e1fad88df6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2543-28ec6aeb80a5e1e1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2561.43c99d6ee914ee6a.js +62 -0
- package/apps/pwa/out/_next/static/chunks/2638.b39ab2f674caa1a5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/272-0eed0fb0aab716b6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7899.597af4a9996c907f.js → 2721.bc9a8f36bd85c07a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2778.67e0c1d484189ccf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2841.10ff309f6a3168af.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2886-9639f97d523d752a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2978.57fb87ce21cc5a85.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3664.15f968c39d15bd28.js +149 -0
- package/apps/pwa/out/_next/static/chunks/3786-935eb3fcde90e496.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3813.df4a01ae89c6f63f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3961-aaae04ed1d32622f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3992.f79c70480e8c4077.js +309 -0
- package/apps/pwa/out/_next/static/chunks/40-413e8641de9ed1e5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4054-521f11a503daed64.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4315.e7356f34c7d8ec79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4489-bf629de6f04f7681.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4540.557733cc88c6a82b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/462.0275b4338c5b6337.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4658-7ed9266098a787e4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4945-0f4046cfc3228ea3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{d70879db.c66c56a95a91efc6.js → 4c5e95ce.83e9b16add6fcd8c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5105-18b4165c5d3a86b0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5205-6992210fe31f45f4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5287-72898b126d722457.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6158.566c890adc06a459.js → 5313.1697309c532b9856.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/{79b78388.663188133449d707.js → 53746282.1be36038d1abd949.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5474.991b90688c3cb321.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5544-871146192d095594.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5578.ac963634373b28c6.js +286 -0
- package/apps/pwa/out/_next/static/chunks/5935.d5e8ca3e096772e8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5967-c0a80f8ffe5d4050.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1268.0840ff91e083fcbd.js → 5974.360f8441eb0ac1ec.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6006.84e8b418a7a0db97.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6013-1267ba7454d6764b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6038-1e0f7ee1086da4cf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/617.709dd0019fe99796.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6261.fb9bf94947094636.js +74 -0
- package/apps/pwa/out/_next/static/chunks/6411.1966866fae31ffa9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6481-b829d99aa8d158f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6541.2b7daa043100d0fa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6571-926ddf024a6ad5df.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6597.2d7a6d77ab485b27.js +64 -0
- package/apps/pwa/out/_next/static/chunks/67.5dd2dc543c2fa3de.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9832.2ab2e818013574c7.js → 6702.ebbd165d5e4a4a14.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6777-312496b0d774719b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/681.0116f7036cfb4e8d.js +32 -0
- package/apps/pwa/out/_next/static/chunks/6871.30b2bf23b3126e36.js +24 -0
- package/apps/pwa/out/_next/static/chunks/6882.737d366d54e29d83.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7035.54c2c535164b1c9b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7086-8bfa7e8f796a03d9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7126.5e5599f251726b67.js +93 -0
- package/apps/pwa/out/_next/static/chunks/7203-47ff0740a293e284.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7268.017f49dafb5f0ff3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7355.5d65a87e9567ae08.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7392.4018c2a62177f296.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6110.1297d4093fdfbb0d.js → 740.90b31555290236a9.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/7471.3a3d1be50559cfb8.js +148 -0
- package/apps/pwa/out/_next/static/chunks/7500.73489a345cef2201.js +226 -0
- package/apps/pwa/out/_next/static/chunks/{9070.067c106528ec1ae6.js → 7518.49161e8f5814abdb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7565.c2f72a15c1ed0396.js +27 -0
- package/apps/pwa/out/_next/static/chunks/7651.6d1c984d3d8587a4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7713.7ab863ed59b2d661.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7793.d7cd2026767ddd54.js +115 -0
- package/apps/pwa/out/_next/static/chunks/7837.c7c267e5dceadb11.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7891.09266382daf70d1c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7905.b230b9d06cb4ad5d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7990-cbf6a01bcbdcb7e6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8119-819c549e0c197786.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8186-74ea19a3700d04a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8194.484f51e273d9abd2.js → 8264.00e434fbd02e9311.js} +16 -16
- package/apps/pwa/out/_next/static/chunks/8267-e7b3262b735ae106.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8386.86518e55b75f7cf5.js +8 -0
- package/apps/pwa/out/_next/static/chunks/{088d8e8a.8069543d082bec12.js → 83cfc5c8.349535543c1548b6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8437.ba6eeeb29590042f.js +23 -0
- package/apps/pwa/out/_next/static/chunks/{1948.5f35b39eef222a94.js → 8438.ee7f0df3b3f6fcb4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8469-1500be57e582296f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8483-7164cecdaf178ca6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{4588.ad3364cb7fb97ada.js → 8532.a86e99acf9b761cb.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/8569.b853a8daa8f31d12.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8722-4c940f02c496ad36.js +1 -0
- package/apps/pwa/out/_next/static/chunks/887-41d0dcf5af48c628.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8925-2abfb48af477fb7f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{88729267.9c6c000157c83f2f.js → 89849432.9eaa2f4c9c010965.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/9039.da86361496797522.js +90 -0
- package/apps/pwa/out/_next/static/chunks/905.d5745a25884754d4.js +94 -0
- package/apps/pwa/out/_next/static/chunks/9089-d909f3d4478b0628.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9096.dc5c7a267f83790c.js +41 -0
- package/apps/pwa/out/_next/static/chunks/93.a567257b6bdb7fe4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9320.419acb9665b46597.js +287 -0
- package/apps/pwa/out/_next/static/chunks/9356.b8ceda6634612ff8.js +79 -0
- package/apps/pwa/out/_next/static/chunks/94.e9e1043b35cd3eed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9400-7498ea90b2e6ee8f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3522-1de1319d028ac89c.js → 9502-32c5dc22e8f120f5.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/9930-58644af9c813d6e1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9932.367cf4725fedd6e2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9935.1628b904cdc8ce0e.js +104 -0
- package/apps/pwa/out/_next/static/chunks/{7630.2b9a68659053ff4e.js → 9976.8f28864317639fe6.js} +16 -16
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-a2aa8709d193ee0d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-3b9fc39362473ae7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f2b62c19ca68fef8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-d7602d489edf5f37.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-24282bf9f90031a5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-8b7c5ab6b5bc8d64.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-4eb84d454f8c3415.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-b2e494bdad57fcb5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/{page-80ed511e02c81412.js → page-c0ab229673a8b0eb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-3e2b45f3c36dbac4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-c22aa511b28743ad.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-26345242e4049f55.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-bf88c0f558d8ef54.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-52fec523e5b5d3ad.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-67723dfdcde92b31.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-a2aa8709d193ee0d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-22725e2570f1126b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-1891335950d68b70.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-3267af1ebdc73cf5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-c314b75c11234998.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-9f9c85415f61782b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-c1d4276059085587.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-833aab587f26ad4c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8526c17ae38e6a50.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-f998637a8402115c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-b7bb53828b17b335.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-1b1850de0257e46d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-6f23159fedb41f67.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-1d809a87139dd024.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-da9e570234d481fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-b92caddb5c715f30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-8ce3a38e210b810a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-57ffeeb6019440fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-abbb417bf514630b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-2c2b3f29cec1efa4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-9369279f72fc7b51.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-64861f14039499c3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-ddc4c93efd8ab94b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-a2aa8709d193ee0d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-901a1391b81053bc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-68d9bbaac9841a58.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-b5c0c996f329371e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-89881ccbcf728978.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-0e87a803b3d7165e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-58d4e5cb30ef4871.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-02ce4c3b5f39e4d2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-e620d1bb8eadcec2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-acf868c64d77f950.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-54fa605fa6dba457.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-bc79f0833533fa84.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-d5c4398eea03d8f3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/b4e12462-5fab4a813a63dadd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/cd39fa05.fac4545f42a3ef70.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{f6b7c9ba.64a895b8b2fedbdc.js → ce383d63.d2ede338788ed18e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{framework-5c169757551134ed.js → framework-b2763b10dc6b3836.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/main-app-27893e93b0ec193d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-f280047194e1daf8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-e90eff2d0b70564c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-70f2d654b3435d34.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-4794f053bd5e4230.js +1 -0
- package/apps/pwa/out/_next/static/css/72becfd42807b50d.css +1 -0
- package/apps/pwa/out/_next/static/css/d0a48dc099ed86cf.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +23 -23
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +23 -23
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +23 -23
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +22 -22
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +22 -22
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +23 -23
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +22 -22
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +23 -23
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +23 -23
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +22 -22
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +23 -23
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +24 -23
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +23 -23
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +23 -23
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +22 -22
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +22 -22
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +23 -23
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +23 -23
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +24 -23
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +22 -22
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +22 -22
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +22 -22
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +23 -23
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +23 -23
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +22 -22
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +22 -22
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +22 -22
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +22 -22
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +23 -23
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +23 -23
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +22 -22
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +23 -23
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +23 -23
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +23 -23
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +23 -23
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +23 -23
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +22 -22
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +23 -23
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +23 -23
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +23 -23
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +23 -23
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +23 -23
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +23 -23
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +23 -23
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +23 -23
- package/catalog/models/anthropic/claude-haiku-5-5.yaml +32 -0
- package/catalog/models/anthropic/claude-opus-5-5.yaml +5 -0
- package/catalog/models/anthropic/claude-sonnet-5-5.yaml +31 -0
- package/catalog/models/anthropic/claude-sonnet-5.yaml +8 -2
- package/catalog/models/openai/gpt-6-astra.yaml +6 -0
- package/catalog/models/openai/gpt-6-luna.yaml +6 -0
- package/catalog/models/openai/gpt-6-sol.yaml +6 -0
- package/catalog/models/openrouter/deepseek-v4-pro-0813.yaml +39 -0
- package/catalog/models/openrouter/deepseek-v4.1-flash.yaml +38 -0
- package/catalog/models/openrouter/glm-5.3-flash.yaml +35 -0
- package/catalog/providers/openrouter.yaml +2 -0
- package/graphs/implement-loop.yaml +31 -0
- package/graphs/log-horizon/log-horizon-watch.yaml +25 -0
- package/graphs/log-horizon/recipes.yaml +3 -0
- package/graphs/ops/harness-gc.yaml +17 -0
- package/graphs/ops/recipes.yaml +3 -0
- package/graphs/overlays/README.md +1 -1
- package/graphs/overlays/stop-heal-ladder.yaml +105 -0
- package/graphs/release/recipes.yaml +1 -1
- package/graphs/release/release-loop.yaml +6 -2
- package/graphs/release-rehearsal/recipes.yaml +12 -0
- package/graphs/release-rehearsal/release-rehearsal.yaml +17 -0
- package/package.json +4 -1
- package/scripts/add-api-key.sh +1 -1
- package/scripts/bench-report.ts +82 -3
- package/scripts/ci-typecheck-changed.ts +80 -14
- package/scripts/fleet/bedrock-pod-secret.ts +122 -0
- package/scripts/fleet/node-setup-linux.sh +97 -0
- package/scripts/fleet/node-setup.sh +11 -0
- package/scripts/fleet/zai-pod-secret.ts +86 -0
- package/scripts/hq/hq-fence-wrapper.sh +148 -0
- package/scripts/land-global-checks.ts +3 -0
- package/scripts/lib/typecheck-ratchet.ts +51 -0
- package/scripts/measure-log-diet-2.ts +213 -0
- package/scripts/measure-tsc-affected-roots.ts +289 -0
- package/scripts/openai-relay-server.ts +5 -1
- package/scripts/release-loop/docs-node.ts +258 -43
- package/scripts/release-loop/gate-executor.ts +28 -0
- package/scripts/release-loop/gate-method.ts +59 -0
- package/scripts/release-loop/gate-node.ts +335 -33
- package/scripts/release-loop/gate-wall.ts +208 -0
- package/scripts/release-loop/known-issues-node.ts +53 -8
- package/scripts/release-loop/publish-at.ts +14 -0
- package/scripts/release-loop/publish-node.ts +17 -2
- package/scripts/release-loop/resume-release.ts +40 -4
- package/scripts/release-loop/resume.ts +94 -0
- package/scripts/release-loop/shard-plan.ts +3 -11
- package/scripts/release-loop/tui-node-checks.ts +22 -0
- package/scripts/release-loop/unattended-release.ts +29 -7
- package/scripts/rhythm/daily.ts +51 -2
- package/scripts/rhythm/weekly.ts +3 -2
- package/scripts/tsc-baseline-cache.ts +147 -0
- package/scripts/tsc-main-safety-net.ts +189 -0
- package/scripts/usage-rollup.ts +64 -8
- package/src/acp/codex-plugins.ts +48 -6
- package/src/agent/monad-agent-turn.ts +67 -5
- package/src/agent/run-context.ts +20 -0
- package/src/agent/tool-profile.ts +28 -2
- package/src/agent-mission/acp-judge.ts +8 -2
- package/src/agent-mission/capabilities-cli.ts +16 -1
- package/src/agent-mission/driver.ts +19 -4
- package/src/agent-mission/pr-review-watch.ts +6 -2
- package/src/agent-mission/resource-ladder.ts +8 -3
- package/src/agent-mission/review-loop.ts +41 -7
- package/src/agent-substrate/acp-implementer.ts +139 -0
- package/src/agent-substrate/acp-reviewer.ts +17 -8
- package/src/agent-substrate/pr-reviewer.ts +191 -28
- package/src/agent-substrate/review-intent.ts +6 -4
- package/src/agent-substrate/self-review-cli.ts +155 -21
- package/src/agent-substrate/shard-boundary-intent.ts +92 -26
- package/src/ask-user-question/pending-questions.ts +8 -2
- package/src/autopilot/build/llm-conflict-merge.ts +216 -12
- package/src/autopilot/codebase-grounding.ts +44 -0
- package/src/autopilot/llm-control-brain.ts +13 -1
- package/src/autopilot/mission-codebase-gate.ts +8 -77
- package/src/autopilot/mission-critique.ts +18 -5
- package/src/autopilot/mission-feasibility-negotiate.ts +11 -10
- package/src/autopilot/mission-trace.ts +12 -1
- package/src/autopilot/pr-manager.ts +12 -3
- package/src/autopilot/system-lookback-run.ts +16 -10
- package/src/bench/bench.ts +183 -0
- package/src/boot/acp-server.ts +4 -7
- package/src/boot/acp-socket-path.ts +7 -0
- package/src/briefing/brief-items.ts +21 -13
- package/src/budget/fetchers/grok.ts +15 -5
- package/src/budget/llm-cost.ts +37 -5
- package/src/budget/types.ts +8 -2
- package/src/budget/unified-usage.ts +88 -6
- package/src/chat/index.ts +12 -3
- package/src/cli/agent-cli.ts +20 -0
- package/src/cli/bench-cli.ts +34 -0
- package/src/cli/brief-cli.ts +27 -21
- package/src/cli/cli-user-error.ts +9 -2
- package/src/cli/guardian-cli.ts +26 -1
- package/src/cli/image-cli.ts +101 -0
- package/src/cli/log-event-names.ts +7 -0
- package/src/cli/logs-cli.ts +1 -0
- package/src/cli/mcp-login.ts +111 -37
- package/src/cli/plugin-cli.ts +21 -1
- package/src/cli/pod-cli.ts +26 -0
- package/src/cli/pr-cli.ts +90 -5
- package/src/cli/pr-land-overlap-evidence.ts +279 -0
- package/src/cli/release-cli.ts +63 -12
- package/src/cli/rfc-status.ts +52 -2
- package/src/cli/self-update.ts +277 -10
- package/src/cli/skills-cli.ts +12 -3
- package/src/cli/tasks-cli.ts +241 -30
- package/src/cli/usage-cli.ts +91 -8
- package/src/context-bus/context-first.ts +51 -2
- package/src/context-bus/context-now-surfaces.ts +13 -2
- package/src/context-bus/long-term-memory.ts +3 -1
- package/src/context-bus/nightly-condense.ts +3 -2
- package/src/core-turn/run-goal-loop.ts +87 -2
- package/src/dashboard/ad-run-setup-bindings.ts +279 -0
- package/src/dashboard/chat-fold.ts +20 -0
- package/src/dashboard/first-screen-band.ts +151 -1
- package/src/dashboard/index.ts +45 -281
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +5 -3
- package/src/dashboard/stale-install-check.ts +24 -2
- package/src/dashboard/turn-stream-formatter.ts +25 -0
- package/src/debug/log-diet.ts +190 -0
- package/src/debug/log.ts +70 -17
- package/src/display/coordinator.ts +25 -2
- package/src/domains/coo-admin-tool.ts +27 -0
- package/src/domains/cron-match.ts +1 -0
- package/src/domains/outbound-alert.ts +17 -6
- package/src/domains/release-tool.ts +158 -10
- package/src/domains/schedule-registry.ts +10 -7
- package/src/domains/schedule-runner.ts +1 -0
- package/src/elanous-config-dir.ts +2 -2
- package/src/elanous-daemon.ts +3 -3
- package/src/error-report/report.ts +21 -4
- package/src/execution-loop/launch-gate.ts +1 -1
- package/src/expression/i18n/messages.en.ts +5 -0
- package/src/expression/i18n/messages.ja.ts +5 -0
- package/src/expression/i18n/messages.ko.ts +5 -0
- package/src/expression/i18n/messages.zh.ts +5 -0
- package/src/expression/i18n/types.ts +8 -0
- package/src/github/pr-labels.ts +2 -0
- package/src/graph-runner/graph-cli.ts +42 -1
- package/src/graph-runner/runner.ts +218 -8
- package/src/grok/usage.ts +23 -2
- package/src/grounding/ref-index.ts +253 -0
- package/src/harness/dispatch-backpressure.ts +267 -0
- package/src/harness/harness-clean.ts +199 -78
- package/src/harness/harness-cli-command.ts +229 -35
- package/src/harness/harness-gc.ts +331 -0
- package/src/harness/harness-seams.ts +2 -20
- package/src/harness/harness-stop.ts +4 -2
- package/src/harness/harness-substrate-default.ts +214 -8
- package/src/harness/launch-registry.ts +6 -0
- package/src/harness/nested-elanous-policy.ts +4 -2
- package/src/harness/worktree-branch-prefix.ts +9 -0
- package/src/hq/fence-wrapper.ts +7 -0
- package/src/hq/fence.ts +187 -0
- package/src/hq/hq.ts +33 -2
- package/src/hq/lease.ts +13 -0
- package/src/hq/seats.ts +133 -21
- package/src/images/image-ledger.ts +179 -0
- package/src/index.ts +250 -69
- package/src/instance/child-scope.ts +32 -1
- package/src/instance/dev-project.ts +133 -0
- package/src/instance/resolve.ts +24 -0
- package/src/instance/spawn-elanous.ts +45 -0
- package/src/intake-plane/collect-github.ts +19 -1
- package/src/intake-plane/digest.ts +45 -20
- package/src/intake-plane/lens.ts +36 -8
- package/src/intake-plane/ref-prefetch.ts +110 -0
- package/src/intelligence-map/model-alias.ts +6 -0
- package/src/intelligence-map/model-catalog.ts +113 -0
- package/src/knowledge/kgs/pack.ts +5 -2
- package/src/knowledge/kgs/sqlite-store.ts +15 -0
- package/src/knowledge/tools/knowledge-query.ts +20 -2
- package/src/knowledge/write.ts +234 -2
- package/src/llm/bedrock-eventstream.ts +215 -0
- package/src/llm/bedrock-provider.ts +248 -0
- package/src/llm/bedrock.ts +491 -0
- package/src/llm/openrouter-byok-cost.ts +96 -0
- package/src/llm/openrouter-deepseek.ts +151 -0
- package/src/llm/openrouter-upstream-pins.config.ts +25 -0
- package/src/llm/provider-credentials.ts +3 -1
- package/src/llm/zai-coding-plan.ts +700 -0
- package/src/llm.ts +383 -41
- package/src/loops/budget.ts +150 -0
- package/src/loops/checker.ts +148 -9
- package/src/loops/orchestrator/resync-shadow.ts +34 -7
- package/src/loops/orchestrator/seat-share.ts +6 -49
- package/src/loops/orchestrator/traffic.ts +5 -81
- package/src/market/signed-index.ts +42 -0
- package/src/maturity/feature-maturity.ts +2 -2
- package/src/mcp/mcp-oauth.ts +136 -17
- package/src/model-tier/llm-tier-map.ts +52 -4
- package/src/models/prompts.ts +23 -1
- package/src/mss/logging/horizon-watch.ts +168 -0
- package/src/mss/logging/log-store.ts +262 -26
- package/src/native-tool-catalog.ts +1 -1
- package/src/nexus/api/consult-requests.ts +7 -10
- package/src/nexus/api/discord-trigger-bot.ts +6 -3
- package/src/nexus/api/error-reports.ts +30 -8
- package/src/nexus/api/http-server.ts +8 -0
- package/src/nexus/api/loop-resources.ts +42 -0
- package/src/nexus/api/missions.ts +73 -2
- package/src/nexus/api/ops-api.ts +18 -0
- package/src/nexus/api/outbound-report.ts +8 -3
- package/src/nexus/api/telegram-trigger-bot.ts +6 -3
- package/src/nexus/index.ts +17 -75
- package/src/nexus/outbound/format.ts +2 -0
- package/src/nexus/outbound/router.ts +198 -13
- package/src/nexus/tools-kind.ts +78 -0
- package/src/oauth/codex-account-rotation.ts +55 -20
- package/src/oauth/codex-account-store.ts +451 -23
- package/src/oauth/fallback-chain.ts +75 -15
- package/src/onboarding/first-run.ts +37 -18
- package/src/onboarding/web-first.ts +20 -2
- package/src/onboarding.ts +2 -1
- package/src/ops/machine-heartbeat.ts +185 -0
- package/src/ops/overview-cache.ts +175 -0
- package/src/ops/overview.ts +8 -0
- package/src/plugins/adapters/claude-package.ts +4 -12
- package/src/plugins/core/capability-policy.ts +141 -0
- package/src/plugins/core/host.ts +21 -8
- package/src/plugins/core/manifest.ts +19 -2
- package/src/plugins/core/trust-store.ts +15 -0
- package/src/plugins/core/types.ts +12 -0
- package/src/plugins/install/market-fetch.ts +68 -16
- package/src/plugins/install/plugin-install.ts +187 -16
- package/src/plugins/maker/plugin-maker.ts +25 -20
- package/src/plugins/maker/wizard-generate.ts +16 -2
- package/src/policy/llm-policy.ts +14 -1
- package/src/process/pid-liveness.ts +1 -1
- package/src/prompt-cache/openai.ts +13 -6
- package/src/prompt-cache/openrouter.ts +94 -0
- package/src/prompt-cache/types.ts +5 -0
- package/src/prompt-enhance/enhance.ts +69 -5
- package/src/prompt-library/family-prompt-coverage.ts +1 -0
- package/src/prompt-library/universal-preamble.ts +2 -2
- package/src/registry/discovery/openrouter-live-resolve.ts +122 -0
- package/src/registry/loader.ts +6 -1
- package/src/release-loop/checklist.ts +24 -9
- package/src/release-loop/feature-store.ts +3 -0
- package/src/release-loop/gate-shards.ts +48 -4
- package/src/release-loop/landed-to-green.ts +81 -0
- package/src/release-loop/light-rc.ts +11 -1
- package/src/release-loop/merged-pr-checklist.ts +10 -0
- package/src/release-loop/placement.ts +107 -23
- package/src/release-loop/rehearsal.ts +317 -0
- package/src/release-loop/release-schedule.ts +13 -1
- package/src/release-loop/rubric-types.ts +1 -0
- package/src/release-loop/rubric.ts +2 -2
- package/src/requirements/requirement-ledger.ts +536 -0
- package/src/retro/daily.ts +26 -12
- package/src/seat-loop/seat-loop.ts +66 -11
- package/src/seat-loop/traffic-signal.ts +44 -0
- package/src/self-dev/ask-launch-flow.ts +2 -2
- package/src/self-dev/dev-cli.ts +109 -11
- package/src/self-dev/dev-pipeline.ts +11 -8
- package/src/self-dev/draft-residue.ts +475 -0
- package/src/self-dev/draft-sweep.ts +254 -10
- package/src/self-dev/draft-triage-rules.ts +27 -1
- package/src/self-dev/graph-journey-route.ts +6 -0
- package/src/self-dev/launch-preflight-text.ts +549 -0
- package/src/self-dev/launch-preflight.ts +10 -545
- package/src/self-dev/orchestrate-cli.ts +82 -5
- package/src/self-dev/orchestrate-summary-line.ts +13 -0
- package/src/self-dev/orchestrate.ts +49 -141
- package/src/self-dev/review-provider-fallback.ts +115 -19
- package/src/self-dev/run-store.ts +1 -3
- package/src/self-dev/run-supervisor.ts +190 -36
- package/src/self-dev/run-types.ts +191 -0
- package/src/self-dev/shard-aggregate.ts +61 -13
- package/src/self-dev/shard-plan.ts +5 -1
- package/src/self-dev/shard-run.ts +23 -5
- package/src/self-dev/supervisor-verdict-edges.ts +3 -4
- package/src/self-implement/blocked-draft-policy.ts +3 -19
- package/src/self-implement/budget-gate.ts +53 -13
- package/src/self-implement/child-context-composition.ts +139 -0
- package/src/self-implement/child-llm-preference.ts +13 -5
- package/src/self-implement/claude-acp-implement.ts +67 -0
- package/src/self-implement/draft-on-stop-mode.ts +18 -0
- package/src/self-implement/flex-signals.ts +59 -0
- package/src/self-implement/follow-up-goals.ts +114 -38
- package/src/self-implement/frozen-merges.ts +8 -3
- package/src/self-implement/gate-baseline-reap.ts +157 -0
- package/src/self-implement/gate-baseline.ts +78 -16
- package/src/self-implement/gate-cli.ts +12 -2
- package/src/self-implement/gate-remote.ts +17 -6
- package/src/self-implement/goal-author-decompose.ts +10 -2
- package/src/self-implement/goal-author.ts +48 -13
- package/src/self-implement/goal-digest.ts +19 -0
- package/src/self-implement/graph-overlay-condition.ts +32 -0
- package/src/self-implement/graph-overlay-yaml.ts +41 -7
- package/src/self-implement/graph-templates.ts +33 -1
- package/src/self-implement/graph-yaml.ts +56 -0
- package/src/self-implement/headless-elanous-driver.ts +11 -9
- package/src/self-implement/heal-intake.ts +5 -1
- package/src/self-implement/host-regate.ts +120 -29
- package/src/self-implement/l8-merge-queue.ts +67 -5
- package/src/self-implement/merge-train.ts +131 -0
- package/src/self-implement/off-diff-evidence.ts +9 -0
- package/src/self-implement/orchestrator.ts +271 -25
- package/src/self-implement/review-finding-recurrence.ts +28 -0
- package/src/self-implement/rework-policy.ts +2 -1
- package/src/self-implement/round-summary-card.ts +63 -0
- package/src/self-implement/run-ledger.ts +4 -3
- package/src/self-implement/run-step-projection.ts +147 -0
- package/src/self-implement/run-stop.ts +1 -1
- package/src/self-implement/running-runs.ts +75 -5
- package/src/self-implement/seams.ts +83 -4
- package/src/self-implement/self-implement-cli.ts +12 -5
- package/src/self-implement/stop-autoheal.ts +69 -2
- package/src/self-implement/train-gate-remote.ts +158 -0
- package/src/session-runtime/index.ts +10 -1
- package/src/session-runtime/retry-api.ts +11 -1
- package/src/session-runtime/retry-policy.ts +2 -1
- package/src/skills/gift-install.ts +119 -0
- package/src/skills/runner.ts +59 -2
- package/src/skills/skill-feedback.ts +31 -1
- package/src/skills/skill-tier.ts +1 -0
- package/src/smart-wizard/capability-index.ts +231 -0
- package/src/smart-wizard/rung.ts +74 -0
- package/src/steward/launch.ts +22 -10
- package/src/steward/triage-budget.ts +62 -0
- package/src/steward/triage.ts +87 -24
- package/src/storage/file-lock.ts +8 -1
- package/src/task-agent/actions.ts +66 -5
- package/src/task-agent/card-evidence.ts +59 -13
- package/src/task-agent/completion-evidence.ts +5 -0
- package/src/task-agent/cover.ts +101 -3
- package/src/task-agent/judge.ts +20 -3
- package/src/task-agent/land-order-ledger.ts +95 -0
- package/src/task-agent/land-worktree.ts +201 -0
- package/src/task-agent/live-move-history.ts +219 -0
- package/src/task-agent/live-moves.ts +451 -34
- package/src/task-agent/parent-host.ts +117 -8
- package/src/task-agent/rejudge-on-head.ts +352 -0
- package/src/task-agent/review-prior-fix.ts +146 -0
- package/src/task-agent/review-result-capture.ts +218 -0
- package/src/task-agent/run-adopt.ts +136 -0
- package/src/task-agent/shadow.ts +154 -14
- package/src/task-agent/task-hand.ts +186 -14
- package/src/task-orchestrator/surfaces/pod-account-broker.ts +38 -5
- package/src/task-orchestrator/surfaces/pod-bedrock-child.ts +74 -0
- package/src/task-orchestrator/surfaces/pod-child-default.ts +50 -0
- package/src/task-orchestrator/surfaces/pod-child-providers.ts +42 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +6 -3
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +94 -7
- package/src/task-orchestrator/surfaces/pod-image-source.ts +212 -0
- package/src/task-orchestrator/surfaces/pod-lease.ts +110 -17
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +32 -3
- package/src/task-orchestrator/surfaces/pod-pool.ts +86 -18
- package/src/task-orchestrator/surfaces/pod-zai-child.ts +101 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +463 -50
- package/src/task-orchestrator/surfaces/self-implement.ts +25 -3
- package/src/telegram-service.ts +5 -1
- package/src/telegram.ts +17 -4
- package/src/time/format.ts +13 -4
- package/src/tool-runtime/goal-author-runtime.ts +1 -0
- package/src/tool-runtime/self-cognition-catalog.ts +23 -0
- package/src/tool-runtime/self-cognition-runtimes.ts +2 -24
- package/src/typecheck-ratchet.ts +1 -0
- package/src/user-config.ts +180 -32
- package/src/ux-sim/slash-menu-frames.ts +168 -0
- package/src/version/packed-revision.json +1 -1
- package/src/web-terminal/preview-tap-registry.ts +5 -2
- package/src/workflow-runtime/triggers/registry.ts +59 -0
- package/src/workflow-runtime/triggers/scheduler.ts +1 -0
- package/src/workflow-runtime/triggers/webhook-source.ts +35 -5
- package/apps/pwa/out/_next/static/chunks/1109.78a7a3288c0259d9.js +0 -148
- package/apps/pwa/out/_next/static/chunks/115.1c6690b8604f81c4.js +0 -32
- package/apps/pwa/out/_next/static/chunks/1262.76a3f08e67c5f816.js +0 -309
- package/apps/pwa/out/_next/static/chunks/1307-08b716476f1d50af.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1358d292.5c87f12cad6eaa91.js +0 -122
- package/apps/pwa/out/_next/static/chunks/1438.27a1a7b8f2308004.js +0 -149
- package/apps/pwa/out/_next/static/chunks/1442.02d85cd88f4c1eac.js +0 -79
- package/apps/pwa/out/_next/static/chunks/1501-1aad662d82ba43f7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1584.9e2dd53da6e6d4b1.js +0 -24
- package/apps/pwa/out/_next/static/chunks/1586.b989b0614afb77e6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1609.2ad355062870151f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1612-f6df0fc0eed7157b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1923.37f1d5fe7ebe3860.js +0 -27
- package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1993.3fd074a2a246234e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/211-9c9090c59e2c5907.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2140.d2daf9ee96e295d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2212.0359851457473efc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/224.583e562365a52f74.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2328.0f37465646bafa7d.js +0 -36
- package/apps/pwa/out/_next/static/chunks/2372.bf71372184991937.js +0 -286
- package/apps/pwa/out/_next/static/chunks/2489-d238d3e2eb5b2bf5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2537-6c289376caa88ca1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2549-a8515c86f9e8cb96.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2655.72cc6a97276c5acc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2743-ef6e8c3ced74cce5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2840-5458ad50c49cac6a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2856.f75d5baef7f49b74.js +0 -1
- package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3344.0de4b0ec5a454b7a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/335.a13f339e97893437.js +0 -94
- package/apps/pwa/out/_next/static/chunks/3394.646348f34b2500be.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3443.6276909b53d72d53.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3495.abd4254d808bf2dc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3721.ba5b82db0d9a5572.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3857-5985236d9307f676.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3919.9ff7dc25afefee33.js +0 -74
- package/apps/pwa/out/_next/static/chunks/3998.bd9a69d9b50e9bd2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4003.ab6db1dc266576c9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4144-fad095b48a1862ee.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4216.e8ac94b9fe6b96ab.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4327.c44dc2b55cb43ee5.js +0 -34
- package/apps/pwa/out/_next/static/chunks/4401.94257d497b6d26bc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4559.17ca2cdbaa8d414f.js +0 -115
- package/apps/pwa/out/_next/static/chunks/4565-3d7e3fdeb43686a8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4706.97e734bd4c9816ca.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4795-f372490b178583fc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4982.6e44352f751b9a1b.js +0 -77
- package/apps/pwa/out/_next/static/chunks/5342.49c5c5a530e62b50.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5360-08a80abdf3a4da99.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5431.66c58ab1327ee0b7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5633-d6852385000738ef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5717.bcfcb96e701b82e0.js +0 -90
- package/apps/pwa/out/_next/static/chunks/5755-30d102586789bf7f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5957.3f1a4012388dd962.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-5b9e8a45a5b0c823.js +0 -1
- package/apps/pwa/out/_next/static/chunks/602.9bf02ac8e9502598.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6020.3a1b347e2d2dfacc.js +0 -8
- package/apps/pwa/out/_next/static/chunks/6375.fce1d174ba6d0324.js +0 -64
- package/apps/pwa/out/_next/static/chunks/6546.92a828aef441271f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/658.957fdb0a8c6c6c8e.js +0 -287
- package/apps/pwa/out/_next/static/chunks/6706.323590aabb8b1293.js +0 -226
- package/apps/pwa/out/_next/static/chunks/6747.b67947f293a4c02d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6868.000b24a3045c5574.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6946.bba74e9c96050f30.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7033.a558f1b2f6912a53.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7065.46029c26c53eac50.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7087.6fae577d09249bc1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/722.de8d9801e0f6d414.js +0 -41
- package/apps/pwa/out/_next/static/chunks/726.83532d69452ee24b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7497.57981e9d7a75a52a.js +0 -24
- package/apps/pwa/out/_next/static/chunks/7672.4af122ad892117c7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/771.6ab889a233e7dae7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8096.f7da0776cfde6300.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8401-35e94e9091f5eb8d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8769.815ca69879f608b4.js +0 -104
- package/apps/pwa/out/_next/static/chunks/8857-aa1cc4ac90af4d40.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8f835b4f.048eeb02299e3e12.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9068.5f2764fccf86c1b2.js +0 -93
- package/apps/pwa/out/_next/static/chunks/9103-60fbef33e66f0a46.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9247-55bcfbb0e8057f59.js +0 -1
- package/apps/pwa/out/_next/static/chunks/935.4ddaa99fc1d1126c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9451.ebfb0dcc9c02250f.js +0 -23
- package/apps/pwa/out/_next/static/chunks/9559.e648369b1d57cf9a.js +0 -62
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-88459e0903da2803.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-d85c43f46f58716b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-4e645600579ae952.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-87edbfd19117823b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-7f0c2b7801ac6a54.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-bf1f030a9d1adffd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-cbaf66b1e1804adf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-9f5610caaa06178c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-ec8f3575e339de74.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-86d753e460f1a989.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-4166a7d51cf0a57d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-2a19c5cd3b562580.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-b6a09cde2e07521a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-f7eae1518ca0c353.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-456a8573ad811381.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-8462d6a543cbb2d9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-2974cdfb87c0f9c9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-f927ffa25f3c86d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-abd86cd832aaa011.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-79daf68f6256adaa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8c3f168fc3f91939.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-786fb4504d0f2fa9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-75209f14597dcb8f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-bf30affe3a85af7e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1187a44b7515434b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-3d0910185a66ed54.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-f401ee1dd5a26e25.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-5dfe7604fd842784.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-76cf838050a8d681.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-7ec8e289b4c14df6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-8c8ac17d52be2a79.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-d359ab048d63a9d9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-b8b345c880bf78d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-f151cc10ad026940.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-9875f103057da433.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-9789328af9a9aa62.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-bf495a1e284f4e00.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-a263d94bca4be22a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-3962eab8ec58d16e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-9abcfac07e02a542.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-bee49f927c333abb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-bac2d56dcb02d955.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-937cbcc13176295c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d09495594f7f480d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-69cb93d1a2e57e0e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-72ec543928d67c3e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-82906eeda0186a7b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/f6231284-45726550aba08fca.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-f2b4016b9b981bc8.js +0 -1
- package/apps/pwa/out/_next/static/css/f2bd5257e6af88d8.css +0 -1
- /package/apps/pwa/out/_next/static/{aBi6EyMtLsrl7J8eH4kF7 → Q-7iofoxOEv1WX18S35of}/_ssgManifest.js +0 -0
|
@@ -16,12 +16,13 @@ import { mergePartialFailures, readPartialPlan, testFileOfId } from './gate-part
|
|
|
16
16
|
import { logPlanWalltime, planDurations, planShards, readFileMemory } from './shard-plan';
|
|
17
17
|
import { deriveCdpTestPatterns } from '../test-deterministic';
|
|
18
18
|
import { emitNodeResult, readGraphContext } from './node-verdict.js';
|
|
19
|
+
import { isGateExecutor, type GateExecutor } from './gate-executor.js';
|
|
19
20
|
import { runPodCommand, parsePodCpu, resolvePodCpu, type RunPodCommandOptions, type PodCommandResult, type PodCpu } from '../../src/task-orchestrator/surfaces/pod-command-job.js';
|
|
20
21
|
import { POD_BUN_CACHE_HOST_PATH, parseInstallSeconds, podBunCacheVolume } from '../../src/task-orchestrator/surfaces/pod-bun-cache.js';
|
|
21
22
|
import { PodPoolScheduler, parsePodPool, checkPodPool } from '../../src/task-orchestrator/surfaces/pod-pool.js';
|
|
22
23
|
import { POD_INSTALL_SLOTS_DEFAULT, POD_INSTALL_SLOTS_HOST_PATH, installSlotScript } from '../../src/task-orchestrator/surfaces/pod-install-slots.js';
|
|
23
24
|
import { gateDepsInstallScript, parseGateDeps } from '../../src/task-orchestrator/surfaces/pod-gate-deps.js';
|
|
24
|
-
import { gateShardsPath, writeGateShards, type GateShard } from '../../src/release-loop/gate-shards.js';
|
|
25
|
+
import { gateShardsPath, recordGateShardsVerdict, writeGateShards, type GateShard } from '../../src/release-loop/gate-shards.js';
|
|
25
26
|
import { defaultKubectl } from '../../src/task-orchestrator/surfaces/self-implement-pod.js';
|
|
26
27
|
|
|
27
28
|
interface CommandResult { rc: number; output: string; passedIds?: string[]; stalledEnv?: Array<{ file: string; reason: 'no-output' }>; timedOut?: boolean }
|
|
@@ -45,6 +46,8 @@ export interface GateOptions {
|
|
|
45
46
|
baselineCommit?: string;
|
|
46
47
|
remote?: string;
|
|
47
48
|
remoteMirror?: string;
|
|
49
|
+
/** GATE-HOST-EXECUTOR — `host` ignores every pod option and sweeps on `remote` (required). Absent = today's choice (pod when a pool is set). */
|
|
50
|
+
executor?: GateExecutor;
|
|
48
51
|
instanceRoot?: string;
|
|
49
52
|
ledgerRoot?: string;
|
|
50
53
|
repo?: string;
|
|
@@ -62,6 +65,16 @@ export interface GateOptions {
|
|
|
62
65
|
admissionWaitSeconds?: number;
|
|
63
66
|
/** GATE-MEM-ADMIT — an injected admission (tests · embedders); config never sets it. Absent = the gate's own pool's. */
|
|
64
67
|
admission?: GateAdmission;
|
|
68
|
+
/** GATE-POD-STALL-FAILFAST — seconds after the source checkout a shard may go without any test output (outside an
|
|
69
|
+
* install-slot wait) before it is stopped as unmeasured (`gatePodStallSeconds`); omitted = {@link GATE_POD_STALL_SECONDS_DEFAULT} · 0 = off. */
|
|
70
|
+
stallSeconds?: number;
|
|
71
|
+
/** GATE-POD-STALL-FAILFAST — injected live Pod log reader / Job stop (tests · embedders); config never sets them.
|
|
72
|
+
* Absent = kubectl on the gate's own pool; an injected pool without `liveLog` runs without the stall watch. */
|
|
73
|
+
liveLog?: (job: { name: string; context?: string }) => string | null;
|
|
74
|
+
/** false = the stop did not take; the watch asks again every poll until it does (void = taken). */
|
|
75
|
+
stopJob?: (job: { name: string; context?: string }) => boolean | void;
|
|
76
|
+
/** How often the stall watch reads the live log (ms); omitted = {@link GATE_POD_STALL_POLL_MS}. */
|
|
77
|
+
stallPollMs?: number;
|
|
65
78
|
/** GATE-LIVE-OBS — the shard state file (`gateShardsPath(version)`) this sweep keeps current; absent = not written. */
|
|
66
79
|
shardsFile?: { path: string; version: string } };
|
|
67
80
|
}
|
|
@@ -75,6 +88,7 @@ export interface GateResult {
|
|
|
75
88
|
knownEnv: number;
|
|
76
89
|
knownEnvCleared: string[];
|
|
77
90
|
stalledEnv: Array<{ file: string; reason: 'no-output' | 'isolated-timeout'; local: 'passed' | 'failed' | 'timeout' }>;
|
|
91
|
+
rcNonzeroNoFailures: Array<{ file: string; rc: number; pass: number; baseline: 'same' | 'clean' | 'missing' }>;
|
|
78
92
|
baselineSource?: 'ledger' | 'instance' | 'cut-logs' | 'swept';
|
|
79
93
|
/** GATE-BASELINE-CACHE — isolated baseline files answered from the ledger (hits) or run now (misses). */
|
|
80
94
|
baselineCache?: { hits: number; misses: number };
|
|
@@ -82,7 +96,9 @@ export interface GateResult {
|
|
|
82
96
|
baselineDeferred?: { cutFiles: number; swept: number };
|
|
83
97
|
durationMs: number;
|
|
84
98
|
error?: string;
|
|
85
|
-
stalledShards?: Array<{ shard: number; files: string[]; reason: 'incomplete' | 'no-output' | 'job-failed' | 'unattributed' | 'timeout'; lastFile?: string; summaryFailures?: number; namedFailures?: number; detail?: string
|
|
99
|
+
stalledShards?: Array<{ shard: number; files: string[]; reason: 'incomplete' | 'no-output' | 'job-failed' | 'exit-code' | 'unattributed' | 'timeout'; lastFile?: string; summaryFailures?: number; namedFailures?: number; detail?: string;
|
|
100
|
+
/** GATE-POD-STALL-FAILFAST — the shard (and its one fresh retry) printed no test output within the stall window: unmeasured. */
|
|
101
|
+
stall?: { seconds: number; sinceCheckoutMs: number } }>;
|
|
86
102
|
partialSummary?: { pass: number; fail: number; errors: number; ran: number; files: number };
|
|
87
103
|
/** GATE-PARTIAL — this verdict re-ran only these files and carried the prior gate's results for the rest. */
|
|
88
104
|
partial?: { priorCommit: string; files: number };
|
|
@@ -254,13 +270,14 @@ export function shardParts(paths: readonly string[], seconds: (file: string) =>
|
|
|
254
270
|
* leaving `install.log`/`install.rc`, `part-<i>.log`/`.rc`/`.junit.xml`, and `shard.timeout` (the part index) when the
|
|
255
271
|
* deadline cut the shard. A part without `.rc` did not finish. The shell itself always exits 0 (as before).
|
|
256
272
|
*/
|
|
257
|
-
export function gateShardShell(o: { parts: readonly (readonly string[])[]; cdpPatterns: readonly string[]; softSeconds: number; cachePrefix: string; installSlots: number; depsDir?: string }): string {
|
|
273
|
+
export function gateShardShell(o: { parts: readonly (readonly string[])[]; cdpPatterns: readonly string[]; softSeconds: number; cachePrefix: string; installSlots: number; depsDir?: string; beatSeconds?: number }): string {
|
|
258
274
|
const run = o.parts.map((part, index) => {
|
|
259
275
|
const ignores = o.cdpPatterns.filter((pattern) => part.includes(pattern)).flatMap((pattern) => ['--path-ignore-patterns', pattern]);
|
|
260
276
|
return `run_part ${index} ${['bun', 'run', 'test:deterministic', ...ignores, ...part.map(asPath)].map(quote).join(' ')}`;
|
|
261
277
|
}).join(' && ');
|
|
262
278
|
return [
|
|
263
279
|
'mkdir -p "$HOME/outbox"; O="$HOME/outbox"',
|
|
280
|
+
...(o.beatSeconds ? gateBeatScript(o.beatSeconds, o.installSlots > 0) : []),
|
|
264
281
|
...(o.cachePrefix ? [o.cachePrefix] : []),
|
|
265
282
|
// Seconds since this container's PID 1 started (the clone ran before this shell) — 0 when /proc cannot tell.
|
|
266
283
|
`pod_age=$(awk -v t="$(cut -d' ' -f22 /proc/1/stat 2>/dev/null)" -v hz="$(getconf CLK_TCK 2>/dev/null)" '{ if (t > 0 && hz > 0) print int($1 - t / hz); else print 0 }' /proc/uptime 2>/dev/null)`,
|
|
@@ -285,9 +302,109 @@ export function gateShardShell(o: { parts: readonly (readonly string[])[]; cdpPa
|
|
|
285
302
|
'true',
|
|
286
303
|
].join('\n');
|
|
287
304
|
}
|
|
305
|
+
/**
|
|
306
|
+
* GATE-POD-STALL-FAILFAST (0.2.24) — the shard shell's live heartbeat on the Pod's stdout (everything else goes to the
|
|
307
|
+
* outbox, which the host sees only after the Job ends). 10-10 0.2.24 gate: the node's containerd was unresponsive, all 28
|
|
308
|
+
* shards ran to their 1697 s deadline with `finishedFiles: 0`, then retries and splits did the same for 2h+ and the node
|
|
309
|
+
* never finished. The beat lets the host see «no test output yet» while the Job runs:
|
|
310
|
+
* `ELANOUS_GATE_BEAT <seq> <slot|install|test> <0|1>` — the last field is 1 once any part log has a byte.
|
|
311
|
+
* It is observation only: the host hashes the shell without it into `runKey`.
|
|
312
|
+
*/
|
|
313
|
+
export function gateBeatScript(beatSeconds: number, installSlots: boolean): string[] {
|
|
314
|
+
return [
|
|
315
|
+
'gate_beat() {',
|
|
316
|
+
' local n=0 st t f',
|
|
317
|
+
' while :; do',
|
|
318
|
+
' n=$((n + 1)); t=0',
|
|
319
|
+
' for f in "$O"/part-*.log; do if [ -s "$f" ]; then t=1; break; fi; done',
|
|
320
|
+
' if [ -f "$O/install.rc" ]; then st=test',
|
|
321
|
+
// A deps miss with no slot line yet = waiting for an install slot (bounded by the slot script; not a stall).
|
|
322
|
+
...(installSlots ? [" elif [ -f \"$O/install.log\" ] && grep -q '^\\[gate\\] deps .* miss' \"$O/install.log\" 2>/dev/null && ! grep -q '^\\[gate\\] install-slot' \"$O/install.log\" 2>/dev/null; then st=slot"] : []),
|
|
323
|
+
' else st=install; fi',
|
|
324
|
+
' echo "ELANOUS_GATE_BEAT $n $st $t"',
|
|
325
|
+
` sleep ${Math.max(1, Math.floor(beatSeconds))} >/dev/null 2>&1 </dev/null`,
|
|
326
|
+
' done',
|
|
327
|
+
'}',
|
|
328
|
+
'gate_beat & gate_beat_pid=$!',
|
|
329
|
+
// Stopped before the shell returns, so no beat line lands between the Job's artifact lines.
|
|
330
|
+
'trap \'kill "$gate_beat_pid" 2>/dev/null; wait "$gate_beat_pid" 2>/dev/null\' EXIT',
|
|
331
|
+
];
|
|
332
|
+
}
|
|
333
|
+
export const GATE_POD_STALL_SECONDS_DEFAULT = 300;
|
|
334
|
+
export const GATE_POD_STALL_POLL_MS = 15_000;
|
|
335
|
+
/** At least ten beats per stall window, never slower than one per 30 s. */
|
|
336
|
+
export const gateStallBeatSeconds = (stallSeconds: number) => Math.min(30, Math.max(1, Math.floor(stallSeconds / 10)));
|
|
337
|
+
export interface ShardLiveLog { checkout: boolean; beat?: { seq: number; stage: 'slot' | 'install' | 'test'; testOutput: boolean } }
|
|
338
|
+
/** What the Pod's live stdout says so far: the source checkout line and the latest beat. */
|
|
339
|
+
export function readShardLiveLog(text: string): ShardLiveLog {
|
|
340
|
+
let checkout = false;
|
|
341
|
+
let beat: ShardLiveLog['beat'];
|
|
342
|
+
for (const line of text.split(/\r?\n/)) {
|
|
343
|
+
if (/^ELANOUS_POD_SOURCE (?:commit|default|pr) /.test(line)) checkout = true;
|
|
344
|
+
const match = /^ELANOUS_GATE_BEAT (\d+) (slot|install|test) ([01])\s*$/.exec(line);
|
|
345
|
+
if (match) beat = { seq: Number(match[1]), stage: match[2] as 'slot' | 'install' | 'test', testOutput: match[3] === '1' };
|
|
346
|
+
}
|
|
347
|
+
return { checkout, ...(beat ? { beat } : {}) };
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* GATE-POD-STALL-FAILFAST — watches one running shard Job. Armed once the source checkout line is seen; disarmed for good
|
|
351
|
+
* the moment any test output exists (that shard then behaves exactly as before). Stalled = `stallSeconds` since the
|
|
352
|
+
* checkout with no test output, not counting time the beat reported an install-slot wait. A silent shell (no beat at all)
|
|
353
|
+
* counts like any other stage. Only successful reads decide; a read failure never stalls a shard. `graceMs` covers the
|
|
354
|
+
* beat's report lag: output that appeared just inside the window is reported by the next beat before the stop decides.
|
|
355
|
+
*/
|
|
356
|
+
export function watchShardStall(o: { stallSeconds: number; pollMs: number; read: () => string | null; onStall: (sinceCheckoutMs: number) => void; now?: () => number; graceMs?: number;
|
|
357
|
+
/** Stops the Job; false = not taken, asked again every poll until it is or the watch ends (a stalled Job must not run to its deadline). */
|
|
358
|
+
stopJob?: () => boolean | void }): { stop(): void; stalled(): { sinceCheckoutMs: number } | undefined } {
|
|
359
|
+
const now = o.now ?? Date.now;
|
|
360
|
+
let checkoutAt: number | undefined;
|
|
361
|
+
let windowStart: number | undefined;
|
|
362
|
+
let lastSeq: number | undefined;
|
|
363
|
+
let hit: { sinceCheckoutMs: number } | undefined;
|
|
364
|
+
let done = false;
|
|
365
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
366
|
+
let ended = false;
|
|
367
|
+
const askStop = () => {
|
|
368
|
+
timer = undefined;
|
|
369
|
+
if (ended) return;
|
|
370
|
+
let taken: boolean | void = false;
|
|
371
|
+
try { taken = o.stopJob ? o.stopJob() : true; } catch { taken = false; }
|
|
372
|
+
if (taken === false && !ended) timer = setTimeout(askStop, o.pollMs);
|
|
373
|
+
};
|
|
374
|
+
const tick = () => {
|
|
375
|
+
timer = undefined;
|
|
376
|
+
if (done) return;
|
|
377
|
+
let text: string | null = null;
|
|
378
|
+
try { text = o.read(); } catch { text = null; }
|
|
379
|
+
if (done) return;
|
|
380
|
+
if (text !== null) {
|
|
381
|
+
const t = now();
|
|
382
|
+
const live = readShardLiveLog(text);
|
|
383
|
+
if (live.beat?.testOutput) { done = true; return; }
|
|
384
|
+
if (live.checkout && checkoutAt === undefined) { checkoutAt = t; windowStart = t; }
|
|
385
|
+
if (live.beat && live.beat.seq !== lastSeq) {
|
|
386
|
+
lastSeq = live.beat.seq;
|
|
387
|
+
if (live.beat.stage === 'slot' && windowStart !== undefined) windowStart = t;
|
|
388
|
+
}
|
|
389
|
+
if (checkoutAt !== undefined && windowStart !== undefined && t - windowStart >= o.stallSeconds * 1000 + (o.graceMs ?? 0)) {
|
|
390
|
+
done = true;
|
|
391
|
+
hit = { sinceCheckoutMs: t - checkoutAt };
|
|
392
|
+
o.onStall(hit.sinceCheckoutMs);
|
|
393
|
+
askStop();
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
timer = setTimeout(tick, o.pollMs);
|
|
398
|
+
};
|
|
399
|
+
timer = setTimeout(tick, o.pollMs);
|
|
400
|
+
return { stop: () => { done = true; ended = true; if (timer) clearTimeout(timer); }, stalled: () => hit };
|
|
401
|
+
}
|
|
288
402
|
/** Removes bun's own summary lines so one summary over several runs can follow (same rule as the sweep's merge). */
|
|
289
403
|
const stripSummary = (output: string) => output.replace(/(?:^|\n)\s*\d+ (?:pass|fail|errors?)\s*(?=\n|$)/g, '\n').replace(/Ran \d+ tests? across \d+ files?\.?/g, '');
|
|
290
404
|
export interface ShardArtifacts {
|
|
405
|
+
/** GATE-POD-STALL-FAILFAST (OP) — every unfinished part ended with a complete clean summary (0 fail · 0 error) but an exit
|
|
406
|
+
* other than 0/1: an exit-code anomaly (a test left process.exitCode set), with those parts' files. */
|
|
407
|
+
exitCodeOnly?: string[];
|
|
291
408
|
/** The verdict text: install log ⊕ every finished part (its summary removed) ⊕ one summary over the finished parts. */
|
|
292
409
|
output?: string;
|
|
293
410
|
junit?: string;
|
|
@@ -314,6 +431,8 @@ export function readShardArtifacts(dir: string, parts: readonly (readonly string
|
|
|
314
431
|
if (installRc !== 0) return { output: install, ...(installRc !== undefined ? { rc: installRc } : {}), timedOut, finished: [] };
|
|
315
432
|
const good: Array<{ log: string; junit?: string; rc: 0 | 1; files: readonly string[] }> = [];
|
|
316
433
|
const bad: string[] = [];
|
|
434
|
+
const exitAnomaly: string[] = [];
|
|
435
|
+
let otherBad = false;
|
|
317
436
|
let badRc: number | undefined;
|
|
318
437
|
parts.forEach((files, index) => {
|
|
319
438
|
const log = read(`part-${index}.log`);
|
|
@@ -325,6 +444,9 @@ export function readShardArtifacts(dir: string, parts: readonly (readonly string
|
|
|
325
444
|
return;
|
|
326
445
|
}
|
|
327
446
|
if (log !== undefined) bad.push(`# part ${index} (${rc === undefined ? 'unfinished' : `rc=${rc}`})\n${log}`);
|
|
447
|
+
if (clean !== undefined && rc !== undefined && rc !== 0 && rc !== 1 && Number(lastMatch(clean, /Ran (\d+) tests? across ([1-9]\d*) files?/)?.[1] ?? 0) > 0
|
|
448
|
+
&& summaryCount(clean, 'fail') === 0 && !(summaryCount(clean, 'error') > 0) && !/^# Unhandled error between tests/m.test(clean)) exitAnomaly.push(...files);
|
|
449
|
+
else otherBad = true;
|
|
328
450
|
// A finished part without a readable summary is not a verdict: its exit, or 2 when it claimed 0|1.
|
|
329
451
|
if (rc !== undefined && badRc === undefined) badRc = rc === 0 || rc === 1 ? 2 : rc;
|
|
330
452
|
});
|
|
@@ -345,6 +467,7 @@ export function readShardArtifacts(dir: string, parts: readonly (readonly string
|
|
|
345
467
|
timedOut: !complete && timedOut && badRc === undefined,
|
|
346
468
|
finished: good.flatMap((part) => [...part.files]),
|
|
347
469
|
...(bad.length ? { unfinishedLog: bad.join('\n') } : {}),
|
|
470
|
+
...(exitAnomaly.length && !otherBad ? { exitCodeOnly: exitAnomaly } : {}),
|
|
348
471
|
};
|
|
349
472
|
}
|
|
350
473
|
|
|
@@ -460,9 +583,54 @@ function failuresOf(run: CommandResult, label: string): SweepFailures {
|
|
|
460
583
|
return { failures, errors: [...new Set(errors)].sort() };
|
|
461
584
|
}
|
|
462
585
|
|
|
586
|
+
/**
|
|
587
|
+
* «N pass · 0 fail · rc=1» from an isolated run is read as `rc-nonzero-no-failures` only when the summary is a complete,
|
|
588
|
+
* clean, single-file summary for the requested file (`Ran N≥1 tests across 1 file` · 0 fail · 0 error · no `(fail)` ·
|
|
589
|
+
* no unhandled error · pass = N · no other test-file header). Every other run is read exactly as before (`failuresOf`,
|
|
590
|
+
* which throws on the incomplete shapes, plus a failure-count/identified-name check).
|
|
591
|
+
*/
|
|
592
|
+
export function readIsolatedRun(run: CommandResult, label: string, file: string):
|
|
593
|
+
| { kind: 'read'; failures: string[]; errors: string[] }
|
|
594
|
+
| { kind: 'rc-nonzero-no-failures'; rc: number; pass: number } {
|
|
595
|
+
const output = stripPodRepoRoot(run.output.replace(/\x1b\[[0-9;]*[A-Za-z]/g, ''));
|
|
596
|
+
const ran = lastMatch(output, /Ran (\d+) tests? across (\d+) files?/);
|
|
597
|
+
// rc 1 only: other non-zero exits with a clean summary keep the established leaked-exit reading (clean) in failuresOf.
|
|
598
|
+
const cleanNonzero = run.rc === 1 && !!ran && Number(ran[1]) >= 1 && Number(ran[2]) === 1
|
|
599
|
+
&& summaryCount(output, 'fail') === 0
|
|
600
|
+
&& (Number.isNaN(summaryCount(output, 'error')) || summaryCount(output, 'error') === 0)
|
|
601
|
+
&& parseFailures(output).length === 0 && !/\(fail\)/.test(output)
|
|
602
|
+
&& !/Unhandled error between tests/.test(output);
|
|
603
|
+
if (!cleanNonzero) {
|
|
604
|
+
const read = failuresOf(run, label);
|
|
605
|
+
// Two (fail) lines folding into one id would undercount the file's failures: unreadable, not fewer.
|
|
606
|
+
const failCount = summaryCount(output, 'fail');
|
|
607
|
+
if (Number.isFinite(failCount) && read.failures.length !== failCount)
|
|
608
|
+
throw new Error(`${label} incomplete (rc=${run.rc}; failure attribution summary=${failCount}, identified=${read.failures.length})`);
|
|
609
|
+
return { kind: 'read', ...read };
|
|
610
|
+
}
|
|
611
|
+
// Bun's file headers, when present, must identify the requested file before the result is recorded or cached.
|
|
612
|
+
// Any test-file path counts: root-level (`other.test.ts:`), nested, or absolute (outside the stripped Pod root).
|
|
613
|
+
for (const raw of output.split(/\r?\n/)) {
|
|
614
|
+
// Any characters but `:` (unicode, spaces, @, +, …) — a header is `<path ending in .test/.spec.ts>:`.
|
|
615
|
+
const header = /^(?:\.\/)?([^\s:][^:]*?\.(?:test|spec)\.[cm]?[jt]sx?):/.exec(raw.trim());
|
|
616
|
+
if (header && header[1] !== file)
|
|
617
|
+
throw new Error(`${label} incomplete (rc=${run.rc}; isolated run attributed to another file: ${header[1]})`);
|
|
618
|
+
}
|
|
619
|
+
const pass = summaryCount(output, 'pass');
|
|
620
|
+
if (pass !== Number(ran![1]))
|
|
621
|
+
throw new Error(`${label} incomplete (rc=${run.rc}; pass=${Number.isNaN(pass) ? 'missing' : pass}; Ran=${ran![1]})`);
|
|
622
|
+
// Validate the requested file as well as the summary; only this one file can own the synthetic id.
|
|
623
|
+
fileOf(`${file} > [rc-nonzero]`);
|
|
624
|
+
return { kind: 'rc-nonzero-no-failures', rc: run.rc, pass };
|
|
625
|
+
}
|
|
626
|
+
|
|
463
627
|
class StalledPodShards extends Error {
|
|
464
628
|
constructor(readonly stalledShards: NonNullable<GateResult['stalledShards']>, readonly partialSummary: NonNullable<GateResult['partialSummary']>) {
|
|
465
|
-
|
|
629
|
+
const unmeasured = stalledShards.filter((item) => item.stall);
|
|
630
|
+
const exitCode = stalledShards.filter((item) => item.reason === 'exit-code');
|
|
631
|
+
super(`pod sweep incomplete: stalled shards ${stalledShards.map(({ shard }) => shard).join(', ')}`
|
|
632
|
+
+ (unmeasured.length ? ` (unmeasured: no test output within ${unmeasured[0]!.stall!.seconds}s of checkout — shards ${[...new Set(unmeasured.map(({ shard }) => shard))].join(', ')})` : '')
|
|
633
|
+
+ (exitCode.length ? ` (exit-code anomaly: 0 fail but exit ≠ 0/1 — ${[...new Set(exitCode.flatMap(({ files }) => files))].join(', ')})` : ''));
|
|
466
634
|
}
|
|
467
635
|
}
|
|
468
636
|
|
|
@@ -652,6 +820,26 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
652
820
|
// An injected pool (tests) runs without admission unless `pod.admission` is passed; the gate's own pool asks it.
|
|
653
821
|
const gateAdmission = pod.admission ?? (poolOverride ? undefined : poolGateAdmission(poolScheduler));
|
|
654
822
|
debug.log('release-loop.gate', 'pod-admission', { waitSeconds: admissionWaitSeconds, source: pod.admissionWaitSeconds !== undefined ? 'config' : 'default', enabled: !!gateAdmission && admissionWaitSeconds > 0 });
|
|
823
|
+
// GATE-POD-STALL-FAILFAST: a shard with no test output within the stall window is stopped and recorded unmeasured.
|
|
824
|
+
const stallSeconds = pod.stallSeconds ?? GATE_POD_STALL_SECONDS_DEFAULT;
|
|
825
|
+
const stallPollMs = pod.stallPollMs ?? GATE_POD_STALL_POLL_MS;
|
|
826
|
+
if (!Number.isFinite(stallSeconds) || stallSeconds < 0 || !Number.isFinite(stallPollMs) || stallPollMs <= 0) throw new Error('invalid pod sweep options');
|
|
827
|
+
// An injected pool (tests) runs without the watch unless `pod.liveLog` is passed — never a real kubectl from a test.
|
|
828
|
+
const liveLog = pod.liveLog ?? (poolOverride ? undefined : (job: { name: string; context?: string }) => {
|
|
829
|
+
if (!job.context) return null;
|
|
830
|
+
const logs = defaultKubectl(['--context', job.context, '-n', 'elanous-test', 'logs', `job/${job.name}`, '-c', 'child', '--tail=400']);
|
|
831
|
+
return logs.status === 0 ? logs.stdout : null;
|
|
832
|
+
});
|
|
833
|
+
const stopJob = pod.stopJob ?? ((job: { name: string; context?: string }) => {
|
|
834
|
+
// Not `delete job`: runPodCommand would poll a missing Job forever. A 1 s deadline makes the Job fail now and
|
|
835
|
+
// runPodCommand returns through its normal path (logs · exit code · Secret cleanup).
|
|
836
|
+
if (!job.context) return false;
|
|
837
|
+
const patched = defaultKubectl(['--context', job.context, '-n', 'elanous-test', 'patch', 'job', job.name, '--type=merge', '-p', JSON.stringify({ spec: { activeDeadlineSeconds: 1 } })]);
|
|
838
|
+
if (patched.status !== 0) debug.log('release-loop.gate', 'pod-shard-stall-stop-failed', { job: job.name, error: patched.stderr.trim().slice(0, 300) }, { level: 'warn' });
|
|
839
|
+
return patched.status === 0;
|
|
840
|
+
});
|
|
841
|
+
const stallWatch = stallSeconds > 0 && !!liveLog;
|
|
842
|
+
debug.log('release-loop.gate', 'pod-stall-watch', { stallSeconds, source: pod.stallSeconds !== undefined ? 'config' : 'default', enabled: stallWatch });
|
|
655
843
|
const jobLogTail = (job: PodCommandResult): string => {
|
|
656
844
|
if (podLogTail) return podLogTail(job);
|
|
657
845
|
for (const member of poolScheduler.members) {
|
|
@@ -692,11 +880,11 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
692
880
|
/** The gate's scheduler, seen through one shard: a slot wait and the Pod start become row states. */
|
|
693
881
|
// GATE-MEM-ADMIT: `placed` releases the shard's admission once a member is chosen — from then on the Job is
|
|
694
882
|
// Pending/Running in the cluster and the next admission reading counts it, so holding the lease would count it twice.
|
|
695
|
-
const trackedPool = (shard: number, retry: boolean, placed: () => void = () => {}): PodPoolScheduler => ({
|
|
883
|
+
const trackedPool = (shard: number, retry: boolean, placed: (member: NonNullable<Awaited<ReturnType<PodPoolScheduler['tryAcquire']>>>) => void = () => {}): PodPoolScheduler => ({
|
|
696
884
|
members: poolScheduler.members,
|
|
697
885
|
tryAcquire: async (...args: Parameters<PodPoolScheduler['tryAcquire']>) => {
|
|
698
886
|
const member = await poolScheduler.tryAcquire(...args);
|
|
699
|
-
if (member) placed();
|
|
887
|
+
if (member) placed(member);
|
|
700
888
|
if (member) markShard(shard, { state: retry ? 'retry' : 'running', startedAt: board[shard]?.startedAt ?? new Date().toISOString() }, ['waitReason']);
|
|
701
889
|
else markShard(shard, { waitReason: 'Pod 자리 대기' });
|
|
702
890
|
return member;
|
|
@@ -709,8 +897,11 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
709
897
|
const shardDeadline = shardDeadlineSeconds(estimated(paths), deadlineSeconds);
|
|
710
898
|
const parts = shardParts(paths, (file) => estimated([file]));
|
|
711
899
|
const memoryLimit = paths.length === 1 && (depth >= 1 || heavy.has(paths[0]!)) ? POD_ISOLATION_MEMORY_LIMIT : undefined;
|
|
712
|
-
const
|
|
713
|
-
const
|
|
900
|
+
const shellOptions = { parts, cdpPatterns, softSeconds: softDeadlineSeconds(shardDeadline), cachePrefix, installSlots };
|
|
901
|
+
const keyShell = gateShardShell(shellOptions);
|
|
902
|
+
// The stall beat only reports; runKey keeps hashing the shell without it, so earlier results stay reusable.
|
|
903
|
+
const podShell = stallWatch ? gateShardShell({ ...shellOptions, beatSeconds: gateStallBeatSeconds(stallSeconds) }) : keyShell;
|
|
904
|
+
const runKey = createHash('sha256').update(JSON.stringify({ pool: pod.pool, podShell: keyShell, memoryLimit: memoryLimit ?? null, deadlineSeconds: shardDeadline, bunCache: bunCache ?? null })).digest('hex');
|
|
714
905
|
let start = Date.now();
|
|
715
906
|
let admitted: GateAdmissionResult | undefined;
|
|
716
907
|
let output: string | undefined;
|
|
@@ -722,6 +913,8 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
722
913
|
let timedOut = false;
|
|
723
914
|
let finished: string[] = [];
|
|
724
915
|
let unfinishedLog: string | undefined;
|
|
916
|
+
let exitCodeOnly: string[] | undefined;
|
|
917
|
+
let stall: { seconds: number; sinceCheckoutMs: number } | undefined;
|
|
725
918
|
// The way down a previous attempt took wins over its own log: that log was not a verdict, or it would have no children.
|
|
726
919
|
// The split this shard would take if it failed here — the same rule as the split code below (isolation at depth 2 ·
|
|
727
920
|
// cap chunks for a big root shard · halves otherwise · none for one file).
|
|
@@ -754,19 +947,34 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
754
947
|
admitted.outcome === 'granted' ? undefined : { level: 'warn' });
|
|
755
948
|
// The admission wait is not the shard's run time: deadline/timeout judgment and the measured duration start here.
|
|
756
949
|
start = Date.now();
|
|
950
|
+
const jobName = `gate-${randomUUID()}`;
|
|
951
|
+
// The member's context is known once the Job is placed; the live log is read (and the Job stopped) there.
|
|
952
|
+
let jobContext: string | undefined;
|
|
953
|
+
const liveJob = () => ({ name: jobName, ...(jobContext ? { context: jobContext } : {}) });
|
|
954
|
+
// Grace = two beats ⊕ one poll: test output that appeared inside the window is seen before the stop.
|
|
955
|
+
const watch = stallWatch ? watchShardStall({ stallSeconds, pollMs: stallPollMs, graceMs: 2 * gateStallBeatSeconds(stallSeconds) * 1000 + stallPollMs, read: () => liveLog!(liveJob()),
|
|
956
|
+
onStall: (sinceCheckoutMs) => debug.log('release-loop.gate', 'pod-shard-stall', { shard, branch, stallSeconds, sinceCheckoutMs }, { level: 'warn' }),
|
|
957
|
+
stopJob: () => {
|
|
958
|
+
try { return stopJob(liveJob()); }
|
|
959
|
+
catch (error) { debug.log('release-loop.gate', 'pod-shard-stall-stop-failed', { job: jobName, error: String(error) }, { level: 'warn' }); return false; }
|
|
960
|
+
} }) : undefined;
|
|
961
|
+
const placed = admitted.release;
|
|
757
962
|
const job = await podCommand({
|
|
758
|
-
pool: pod.pool, poolScheduler: trackedPool(shard, depth > 0 || branch !== '',
|
|
963
|
+
pool: pod.pool, poolScheduler: trackedPool(shard, depth > 0 || branch !== '', (member) => { jobContext = member.context; placed(); }), clone: true, source: { kind: 'commit', sha: commit }, deadlineSeconds: shardDeadline,
|
|
759
964
|
...(bunCache ? { bunCache } : {}),
|
|
760
965
|
...(installSlots > 0 ? { installSlots: POD_INSTALL_SLOTS_HOST_PATH } : {}),
|
|
761
966
|
...(pod.cpu ? { cpu: pod.cpu } : {}),
|
|
762
967
|
// 실패 뒤 다시 도는 파일 하나짜리 Job 은 메모리 한도를 올린다 — 16Gi 에선 무거운 한 파일이 혼자서도 OOM 이었다(09-30 `unwired-exports`).
|
|
763
968
|
...(memoryLimit ? { memoryLimit } : {}),
|
|
764
|
-
name:
|
|
969
|
+
name: jobName,
|
|
765
970
|
command: ['bash', '-lc', podShell],
|
|
766
|
-
});
|
|
971
|
+
}).finally(() => watch?.stop());
|
|
767
972
|
jobExitCode = job.exitCode;
|
|
768
973
|
const read = readShardArtifacts(job.artifactsDir, parts);
|
|
769
|
-
({ output, junit, rc, timedOut, finished, unfinishedLog } = read);
|
|
974
|
+
({ output, junit, rc, timedOut, finished, unfinishedLog, exitCodeOnly } = read);
|
|
975
|
+
// A stall counts only when the Job also left no verdict (a failed stop can let a late shard finish after all).
|
|
976
|
+
const stalledAt = watch?.stalled();
|
|
977
|
+
if (stalledAt && !finished.length && rc === undefined) stall = { seconds: stallSeconds, sinceCheckoutMs: stalledAt.sinceCheckoutMs };
|
|
770
978
|
// A Job the cluster cut at its own deadline leaves no outbox at all: past the deadline with nothing is a timeout too.
|
|
771
979
|
if (!timedOut && output === undefined && job.exitCode !== 0 && Date.now() - start >= shardDeadline * 1000) timedOut = true;
|
|
772
980
|
lastFile = output === undefined && unfinishedLog === undefined ? undefined : lastStartedTestFile(`${output ?? ''}\n${unfinishedLog ?? ''}`, paths);
|
|
@@ -795,13 +1003,14 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
795
1003
|
const unfinishedDestination = join(logDir, `pod-${shard}${branch}.unfinished.log`);
|
|
796
1004
|
if (unfinishedLog !== undefined) writeFileSync(unfinishedDestination, unfinishedLog, { mode: 0o600 });
|
|
797
1005
|
else rmSync(unfinishedDestination, { force: true });
|
|
798
|
-
writeFileSync(join(logDir, `pod-${shard}${branch}.json`), JSON.stringify({ durationMs, rc: rc ?? null,
|
|
1006
|
+
writeFileSync(join(logDir, `pod-${shard}${branch}.json`), JSON.stringify({ durationMs, rc: rc ?? null,
|
|
1007
|
+
...(stall ? { reason: 'stall', finishedFiles: 0, stallSeconds: stall.seconds, sinceCheckoutMs: stall.sinceCheckoutMs } : timedOut ? { reason: 'timeout', finishedFiles: finished.length } : {}), files: paths, plannedSeconds: estimated(paths), deadlineSeconds: shardDeadline, shardCount: shards.length, commit, runKey, attemptId, ...(parentAttempt ? { parentAttempt } : {}), jobExitCode: jobExitCode ?? null, jobFailed,
|
|
799
1008
|
...(admitted && admitted.outcome !== 'off' ? { admission: admitted.outcome, admissionWaitMs: admitted.waitedMs } : {}) }) + '\n', { mode: 0o600 });
|
|
800
1009
|
}
|
|
801
1010
|
const installSeconds = parseInstallSeconds(output ?? '');
|
|
802
1011
|
// GATE-TIMEOUT-HEAL ③: a cut shard is «reason: timeout» (with the files that still finished), not a bare rc=null.
|
|
803
1012
|
if (!reused && !descent) debug.log('release-loop.gate', 'pod-shard', { shard, files: paths, durationMs, rc: rc ?? null, attempt: depth + 1, installSeconds, gateDeps: parseGateDeps(output ?? ''), deadlineSeconds: shardDeadline, parts: parts.length,
|
|
804
|
-
...(timedOut ? { reason: 'timeout', finishedFiles: finished.length } : {}) });
|
|
1013
|
+
...(stall ? { reason: 'stall', finishedFiles: 0, stallSeconds: stall.seconds } : timedOut ? { reason: 'timeout', finishedFiles: finished.length } : {}) });
|
|
805
1014
|
if (!reused && !descent) markShard(shard, { ...(installSeconds !== null ? { installSec: installSeconds } : {}), ...(rc !== undefined ? { rc } : {}), ...(timedOut ? { state: 'timeout' as const } : {}) });
|
|
806
1015
|
let clean = output?.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '');
|
|
807
1016
|
const ran = clean && lastMatch(clean, /Ran (\d+) tests? across (\d+) files?/);
|
|
@@ -812,7 +1021,13 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
812
1021
|
let namedFailures: number | undefined;
|
|
813
1022
|
let unattributedDetail: string | undefined;
|
|
814
1023
|
if (descent === 'split') reason = 'incomplete';
|
|
1024
|
+
// GATE-POD-STALL-FAILFAST: stopped with no test output — the existing «no output» reason (one fresh retry).
|
|
1025
|
+
else if (stall) reason = 'no-output';
|
|
815
1026
|
else if (timedOut) reason = 'timeout';
|
|
1027
|
+
// GATE-POD-STALL-FAILFAST (OP): a complete summary with 0 fail · 0 error but an exit other than 0/1 is an exit-code
|
|
1028
|
+
// anomaly (0.2.23: a test left process.exitCode set → «0 fail · rc 3» read as a stalled shard). Same split/isolation
|
|
1029
|
+
// path as job-failed — only the label differs, so the verdict names the file instead of saying «stalled».
|
|
1030
|
+
else if (!jobFailed && exitCodeOnly?.length && rc !== undefined && rc !== 0 && rc !== 1) reason = 'exit-code';
|
|
816
1031
|
else if (jobFailed || (jobExitCode !== undefined && jobExitCode !== 0) || (rc !== undefined && rc !== 0 && rc !== 1)) reason = 'job-failed';
|
|
817
1032
|
else if (!clean || rc === undefined) reason = 'no-output';
|
|
818
1033
|
else if (!ran || !Number.isFinite(passes) || !Number.isFinite(fails)
|
|
@@ -859,6 +1074,9 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
859
1074
|
debug.log('release-loop.gate', 'pod-shard-retry', { shard, files: paths, reason, outcome: retry.stalled[0]?.reason ?? 'ok' });
|
|
860
1075
|
return retry;
|
|
861
1076
|
}
|
|
1077
|
+
// GATE-POD-STALL-FAILFAST: a stall after the fresh retry is unmeasured — no split, no isolation: smaller Pods on the
|
|
1078
|
+
// same stalled node only re-measure the stall (10-10: retries and splits repeated it for 2h+).
|
|
1079
|
+
if (stall && reason === 'no-output') return { runs: [], stalled: [{ shard, files: paths, reason, stall }] };
|
|
862
1080
|
if (reason) {
|
|
863
1081
|
// GATE-SKIP-KNOWN-RETRY — a shard whose summary is complete but whose failures could not be read back (unattributed)
|
|
864
1082
|
// and whose every named failure is one the previous release already had is not re-run: re-running only re-measures
|
|
@@ -950,7 +1168,11 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
950
1168
|
const total = measured.reduce((sum, run) => ({ pass: sum.pass + run.pass, fail: sum.fail + run.fail,
|
|
951
1169
|
errors: sum.errors + run.errors, ran: sum.ran + run.ran, files: sum.files + run.files }),
|
|
952
1170
|
{ pass: 0, fail: 0, errors: 0, ran: 0, files: 0 });
|
|
953
|
-
|
|
1171
|
+
const unmeasured = stalledShards.filter((item) => item.stall);
|
|
1172
|
+
if (unmeasured.length) debug.log('release-loop.gate', 'pod-sweep-unmeasured', { shards: [...new Set(unmeasured.map((item) => item.shard))], files: unmeasured.reduce((sum, item) => sum + item.files.length, 0),
|
|
1173
|
+
stallSeconds, measuredFiles: total.files }, { level: 'warn' });
|
|
1174
|
+
// An unmeasured (stalled) shard is never handed to the host re-run as an environment stall: the gate fails now.
|
|
1175
|
+
if (stalledShards.some((item) => item.reason !== 'no-output' || item.files.length !== 1 || item.stall)) throw new StalledPodShards(stalledShards, total);
|
|
954
1176
|
if (total.ran === 0 && total.errors === 0 && !stalledShards.length) throw new Error('sweep incomplete: no tests ran');
|
|
955
1177
|
const outputs = measured.map((run) => run.output.replace(/(?:^|\n)\s*\d+ (?:pass|fail|errors?)\s*(?=\n|$)/g, '\n').replace(/Ran \d+ tests? across \d+ files?\.?/g, ''));
|
|
956
1178
|
return { rc: total.fail + total.errors ? 1 : 0, output: outputs.join('\n') + `\n${total.pass} pass\n${total.fail} fail\n${total.errors} errors\nRan ${total.ran} tests across ${total.files} files.\n`,
|
|
@@ -1149,7 +1371,7 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
1149
1371
|
return baselineCommit(root, version);
|
|
1150
1372
|
};
|
|
1151
1373
|
const repo = resolve(opts.repo ?? process.cwd());
|
|
1152
|
-
const result: GateResult = { outcome: 'error', commit: opts.commit, introduced: [], preexisting: 0, fixed: 0, knownEnv: 0, knownEnvCleared: [], stalledEnv: [], durationMs: 0 };
|
|
1374
|
+
const result: GateResult = { outcome: 'error', commit: opts.commit, introduced: [], preexisting: 0, fixed: 0, knownEnv: 0, knownEnvCleared: [], stalledEnv: [], rcNonzeroNoFailures: [], durationMs: 0 };
|
|
1153
1375
|
let work: string | undefined;
|
|
1154
1376
|
let baseSnapshot = false;
|
|
1155
1377
|
let cutFailures: SweepFailures | undefined;
|
|
@@ -1345,12 +1567,15 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
1345
1567
|
result.stalledEnv.push({ file, reason: 'isolated-timeout', local: 'timeout' });
|
|
1346
1568
|
continue;
|
|
1347
1569
|
}
|
|
1348
|
-
const
|
|
1570
|
+
const cutReading = cutCheck === undefined ? undefined : readIsolatedRun(cutCheck, `cut isolated ${file}`, file);
|
|
1571
|
+
const rcOnly = cutReading?.kind === 'rc-nonzero-no-failures' ? cutReading : undefined;
|
|
1572
|
+
const rcOnlyId = `${file} > [rc-nonzero]`;
|
|
1573
|
+
const isolatedCut: SweepFailures = cutReading === undefined
|
|
1349
1574
|
? { failures: cut.failures.filter((id) => fileOf(id) === file), errors: cut.errors.filter((id) => fileOf(id) === file) }
|
|
1350
|
-
:
|
|
1575
|
+
: cutReading.kind === 'read' ? cutReading : { failures: [], errors: [rcOnlyId] };
|
|
1351
1576
|
if (cutCheck !== undefined) remember({ commit: opts.commit, file, host: cacheHost, failures: isolatedCut.failures, errors: isolatedCut.errors, missing: false });
|
|
1352
1577
|
const reproduced = new Set([...isolatedCut.failures, ...isolatedCut.errors]);
|
|
1353
|
-
let candidates = diff.newFailures.filter((id) => fileOf(id) === file && reproduced.has(id));
|
|
1578
|
+
let candidates = rcOnly ? [rcOnlyId] : diff.newFailures.filter((id) => fileOf(id) === file && reproduced.has(id));
|
|
1354
1579
|
// GATE-INTRO-RECHECK — a new failure that is only bun's «timed out» shape gets one more isolated run with a generous
|
|
1355
1580
|
// per-test limit, on the same runner (node-b when the gate is remote). Passing there = flaky under load (warning);
|
|
1356
1581
|
// an assertion failure, a failure again, or a run that cannot be read stays introduced.
|
|
@@ -1382,7 +1607,9 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
1382
1607
|
}
|
|
1383
1608
|
if (candidates.length === 0) continue;
|
|
1384
1609
|
let oldFailures: Set<string>;
|
|
1610
|
+
let baselineMissing = false;
|
|
1385
1611
|
const hit = baseCache.get(`${file}\0${cacheHost}`);
|
|
1612
|
+
if (hit?.missing) baselineMissing = true;
|
|
1386
1613
|
if (hit) {
|
|
1387
1614
|
cacheStats.hits++;
|
|
1388
1615
|
oldFailures = new Set([...hit.failures, ...hit.errors]);
|
|
@@ -1399,13 +1626,22 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
1399
1626
|
const lookup = await runner.command('git', ['ls-tree', '--name-only', baseSha, '--', file], opts.remote ? baselineTree : repo);
|
|
1400
1627
|
if (lookup.rc !== 0 || lookup.output.trim()) throw new Error(`baseline isolated run incomplete: ${file}`);
|
|
1401
1628
|
oldFailures = new Set();
|
|
1629
|
+
baselineMissing = true;
|
|
1402
1630
|
remember({ commit: baseSha, file, host: cacheHost, failures: [], errors: [], missing: true });
|
|
1403
1631
|
} else {
|
|
1404
|
-
const
|
|
1632
|
+
const reading = readIsolatedRun(previous, `baseline isolated ${file}`, file);
|
|
1633
|
+
const prior: SweepFailures = reading.kind === 'read' ? reading : { failures: [], errors: [rcOnlyId] };
|
|
1634
|
+
if (reading.kind === 'rc-nonzero-no-failures') debug.log('release-loop.gate', 'isolated-rc-nonzero-no-failures',
|
|
1635
|
+
{ file, label: 'baseline isolated', rc: reading.rc, pass: reading.pass, baseline: 'same' });
|
|
1405
1636
|
oldFailures = new Set([...prior.failures, ...prior.errors]);
|
|
1406
1637
|
remember({ commit: baseSha, file, host: cacheHost, failures: prior.failures, errors: prior.errors, missing: false });
|
|
1407
1638
|
}
|
|
1408
1639
|
}
|
|
1640
|
+
if (rcOnly) {
|
|
1641
|
+
const baseline = oldFailures.has(rcOnlyId) ? 'same' : baselineMissing ? 'missing' : 'clean';
|
|
1642
|
+
result.rcNonzeroNoFailures.push({ file, rc: rcOnly.rc, pass: rcOnly.pass, baseline });
|
|
1643
|
+
debug.log('release-loop.gate', 'isolated-rc-nonzero-no-failures', { file, label: 'cut isolated', rc: rcOnly.rc, pass: rcOnly.pass, baseline });
|
|
1644
|
+
}
|
|
1409
1645
|
for (const id of candidates) {
|
|
1410
1646
|
if (oldFailures.has(id)) result.preexisting++;
|
|
1411
1647
|
else result.introduced.push(id);
|
|
@@ -1456,6 +1692,15 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
1456
1692
|
}
|
|
1457
1693
|
}
|
|
1458
1694
|
if (result.outcome !== 'error') debug.log('release-loop.gate', 'judged', { version: opts.version, commit: opts.commit, introduced: result.introduced.length, preexisting: result.preexisting });
|
|
1695
|
+
// GATE-LIVE-OBS-RC-WORDING ⓒ — the shards board says «판정 전» until this lands; then «새 실패 N» only (UX · gate-shards.ts).
|
|
1696
|
+
// Same ledger as the board's own writes (sweep's shardsFile). A write failure is logged and never changes the verdict.
|
|
1697
|
+
if (result.outcome !== 'error' && opts.pod) {
|
|
1698
|
+
const path = opts.ledgerRoot ? gateShardsPath(opts.version, opts.ledgerRoot) : gateShardsPath(opts.version);
|
|
1699
|
+
try {
|
|
1700
|
+
const written = recordGateShardsVerdict(path, { introduced: result.introduced.length, preexisting: result.preexisting });
|
|
1701
|
+
debug.log('release-loop.gate', 'shards-verdict', { version: opts.version, path, written, introduced: result.introduced.length, preexisting: result.preexisting });
|
|
1702
|
+
} catch (error) { debug.log('release-loop.gate', 'shards-verdict-write-failed', { path, error: String(error) }); }
|
|
1703
|
+
}
|
|
1459
1704
|
return result;
|
|
1460
1705
|
}
|
|
1461
1706
|
|
|
@@ -1464,10 +1709,36 @@ export function graphGateResult(result: GateResult, graph: boolean) {
|
|
|
1464
1709
|
verdict: result.outcome === 'ok' ? 'pass' as const : 'fail' as const,
|
|
1465
1710
|
summary: (result.partial ? `부분 재검 ${result.partial.files}파일(앞 게이트 ${result.partial.priorCommit.slice(0, 9)} 결과 이음) · ` : '')
|
|
1466
1711
|
+ (result.outcome === 'ok' ? `새 회귀 ${result.introduced.length} · 기존 ${result.preexisting} · 고침 ${result.fixed ?? '못 셈(기준선 미루기)'}` : `게이트 ${result.outcome}: ${result.error ?? result.introduced.length + ' new regressions'}`) + ` · 환경 알려진 실패 ${result.knownEnv}`
|
|
1712
|
+
+ (result.rcNonzeroNoFailures.length ? ` · ⚠ 종료코드만 비영 ${result.rcNonzeroNoFailures.length}` : '')
|
|
1467
1713
|
+ (result.loadFlaky?.length ? ` · ⚠ 부하 흔들림 ${result.loadFlaky.length}(시간 초과 꼴 · ${GATE_TIMEOUT_RECHECK_MS / 1000}s 재실행 통과 · 막지 않음)` : '')
|
|
1468
1714
|
+ (result.stalledEnv.length ? ` · 환경 멈춤 ${result.stalledEnv.length} (Pod 밖 통과 ${result.stalledEnv.filter((item) => item.local === 'passed').length})` : '') };
|
|
1469
1715
|
}
|
|
1470
1716
|
|
|
1717
|
+
/** GATE-HOST-EXECUTOR — which executor produced (or will produce) this verdict, for the node output and observation. */
|
|
1718
|
+
export function gateExecutorOf(opts: Pick<GateOptions, 'pod' | 'remote'>): { executor: GateExecutor; host: string | null } {
|
|
1719
|
+
// A Pod sweep runs in shard Pods even when gateRemote is also set (that host only holds the mirror) — no SSH host judged it.
|
|
1720
|
+
return opts.pod ? { executor: 'pod', host: null } : { executor: 'host', host: opts.remote ?? 'local' };
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
/** What was asked for, read best-effort when the options themselves were refused — so a failed gate still says which executor. */
|
|
1724
|
+
export function requestedGateExecutor(args: string[], env: NodeJS.ProcessEnv): { executor: string | null; host: string | null } {
|
|
1725
|
+
let executor: unknown;
|
|
1726
|
+
let host: unknown;
|
|
1727
|
+
try {
|
|
1728
|
+
if (env.ELANOUS_GRAPH_CONTEXT && args.every((arg) => arg === '--json')) {
|
|
1729
|
+
const input = readGraphContext(env).input as Record<string, unknown>;
|
|
1730
|
+
executor = input.gateExecutor ?? (typeof input.gatePodPool === 'string' ? 'pod' : undefined);
|
|
1731
|
+
host = input.gateRemote;
|
|
1732
|
+
}
|
|
1733
|
+
} catch { /* the refusal itself is reported as the error */ }
|
|
1734
|
+
for (let i = 0; i < args.length; i++) {
|
|
1735
|
+
if (args[i] === '--executor') executor = args[i + 1];
|
|
1736
|
+
else if (args[i] === '--remote') host = args[i + 1];
|
|
1737
|
+
}
|
|
1738
|
+
// Same rule as gateExecutorOf: a pod gate names no SSH host.
|
|
1739
|
+
return { executor: typeof executor === 'string' ? executor : null, host: executor !== 'pod' && typeof host === 'string' ? host : null };
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1471
1742
|
export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOptions | 'help' {
|
|
1472
1743
|
const context = env.ELANOUS_GRAPH_CONTEXT && args.every((arg) => arg === '--json') ? readGraphContext(env) : undefined;
|
|
1473
1744
|
const fromGraph: Record<string, unknown> = context?.input ?? {};
|
|
@@ -1475,6 +1746,9 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
|
|
|
1475
1746
|
if (typeof graphCommit === 'string') fromGraph.commit = graphCommit;
|
|
1476
1747
|
const baselineCommit = context?.outputs.cutoff?.baseline as { sha?: unknown } | undefined;
|
|
1477
1748
|
if (typeof baselineCommit?.sha === 'string') fromGraph.previousCommit = baselineCommit.sha;
|
|
1749
|
+
if (fromGraph.gateExecutor !== undefined && !isGateExecutor(fromGraph.gateExecutor)) throw new Error(`gateExecutor must be pod or host: ${String(fromGraph.gateExecutor)}`);
|
|
1750
|
+
// GATE-HOST-EXECUTOR: a host gate never reads the pod settings (a bad pod value must not block the fallback executor).
|
|
1751
|
+
const hostExecutor = fromGraph.gateExecutor === 'host';
|
|
1478
1752
|
const opts: GateOptions = {
|
|
1479
1753
|
commit: typeof fromGraph.commit === 'string' ? fromGraph.commit : '',
|
|
1480
1754
|
version: typeof fromGraph.version === 'string' ? fromGraph.version : '',
|
|
@@ -1483,17 +1757,19 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
|
|
|
1483
1757
|
baselineCommit: typeof fromGraph.previousCommit === 'string' ? fromGraph.previousCommit : undefined,
|
|
1484
1758
|
remote: typeof fromGraph.gateRemote === 'string' ? fromGraph.gateRemote : undefined,
|
|
1485
1759
|
remoteMirror: typeof fromGraph.gateRemoteMirror === 'string' ? fromGraph.gateRemoteMirror : undefined,
|
|
1486
|
-
pod: typeof fromGraph.gatePodPool === 'string' ? {
|
|
1760
|
+
pod: !hostExecutor && typeof fromGraph.gatePodPool === 'string' ? {
|
|
1487
1761
|
pool: fromGraph.gatePodPool,
|
|
1488
1762
|
...(fromGraph.gatePodShards !== undefined ? { shards: Number(fromGraph.gatePodShards) } : {}),
|
|
1489
1763
|
...(fromGraph.gatePodShardTimeoutSeconds !== undefined ? { shardTimeoutSeconds: Number(fromGraph.gatePodShardTimeoutSeconds) } : {}),
|
|
1490
1764
|
...(typeof fromGraph.gatePodBunCache === 'string' && fromGraph.gatePodBunCache.trim() ? { bunCache: fromGraph.gatePodBunCache } : {}),
|
|
1491
1765
|
...(fromGraph.gatePodInstallSlots !== undefined ? { installSlots: Number(fromGraph.gatePodInstallSlots) } : {}),
|
|
1492
1766
|
...(fromGraph.gatePodAdmissionWaitSeconds !== undefined ? { admissionWaitSeconds: Number(fromGraph.gatePodAdmissionWaitSeconds) } : {}),
|
|
1767
|
+
...(fromGraph.gatePodStallSeconds !== undefined ? { stallSeconds: Number(fromGraph.gatePodStallSeconds) } : {}),
|
|
1493
1768
|
...(fromGraph.gatePodFreshShards === true ? { freshShards: true } : {}),
|
|
1494
1769
|
...((cpu) => cpu ? { cpu } : {})(parsePodCpu(fromGraph.gatePodCpu, 'release.loop.gatePodCpu')),
|
|
1495
1770
|
} : undefined,
|
|
1496
1771
|
};
|
|
1772
|
+
if (isGateExecutor(fromGraph.gateExecutor)) opts.executor = fromGraph.gateExecutor;
|
|
1497
1773
|
let freshShards = false;
|
|
1498
1774
|
for (let i = 0; i < args.length; i++) {
|
|
1499
1775
|
const arg = args[i];
|
|
@@ -1501,6 +1777,12 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
|
|
|
1501
1777
|
if (arg === '--json') continue;
|
|
1502
1778
|
// Applied after the loop and only to a Pod gate — on its own it must not turn a local gate into a Pod one (review r5).
|
|
1503
1779
|
if (arg === '--fresh-shards') { freshShards = true; continue; }
|
|
1780
|
+
if (arg === '--executor') {
|
|
1781
|
+
const value = args[++i];
|
|
1782
|
+
if (!isGateExecutor(value)) throw new Error(`--executor must be pod or host: ${value ?? ''}`);
|
|
1783
|
+
opts.executor = value;
|
|
1784
|
+
continue;
|
|
1785
|
+
}
|
|
1504
1786
|
const key = ({ '--commit': 'commit', '--version': 'version', '--baseline-version': 'baselineVersion', '--baseline-commit': 'baselineCommit', '--remote': 'remote', '--remote-mirror': 'remoteMirror', '--pod-pool': 'pod', '--pod-shards': 'pod', '--pod-shard-timeout-seconds': 'pod' } as Record<string, keyof GateOptions>)[arg!];
|
|
1505
1787
|
if (!key) throw new Error(`unknown option: ${arg}`);
|
|
1506
1788
|
const value = args[++i];
|
|
@@ -1516,29 +1798,49 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
|
|
|
1516
1798
|
else if (key === 'remoteMirror') opts.remoteMirror = value;
|
|
1517
1799
|
}
|
|
1518
1800
|
if (freshShards && opts.pod) opts.pod = { ...opts.pod, freshShards: true };
|
|
1801
|
+
// GATE-HOST-EXECUTOR: the host executor is the existing non-pod sweep on gateRemote — a configured pool must not pull it
|
|
1802
|
+
// back into Pods, and without a host it would silently sweep on the runner's own machine.
|
|
1803
|
+
if (opts.executor === 'host') {
|
|
1804
|
+
if (!opts.remote) throw new Error('gateExecutor host requires gateRemote (the SSH host whose clean clone runs the sweep)');
|
|
1805
|
+
opts.pod = undefined;
|
|
1806
|
+
}
|
|
1807
|
+
// An explicit pod choice must not quietly become a host sweep for want of a pool (absent executor = today's behaviour).
|
|
1808
|
+
if (opts.executor === 'pod' && !opts.pod?.pool) throw new Error('gateExecutor pod requires gatePodPool');
|
|
1519
1809
|
return opts;
|
|
1520
1810
|
}
|
|
1521
1811
|
|
|
1522
|
-
|
|
1812
|
+
/** The node's run: judge, emit the graph result line, and return the process exit code (ok 0 · regression 1 · else 2).
|
|
1813
|
+
* `runner` is a seam for tests; the process entry passes none. */
|
|
1814
|
+
export async function runGateNode(argv: string[], env: NodeJS.ProcessEnv, runner?: GateRunner): Promise<number> {
|
|
1523
1815
|
const start = Date.now();
|
|
1524
|
-
// The graph spawns this file as its own process, which inherits no daemon sink — without this every
|
|
1525
|
-
// `release-loop.gate`/`release.gate`/`pod.pool` event stayed in the file trail and `elanous logs` read 0 rows.
|
|
1526
|
-
await registerStandaloneLogSink('release-loop');
|
|
1527
1816
|
let result: GateResult;
|
|
1528
1817
|
let version = '';
|
|
1818
|
+
let placement: { executor: string | null; host: string | null } | undefined;
|
|
1529
1819
|
try {
|
|
1530
|
-
const opts = parseOptions(
|
|
1820
|
+
const opts = parseOptions(argv, env);
|
|
1821
|
+
if (opts !== 'help') {
|
|
1822
|
+
placement = gateExecutorOf(opts);
|
|
1823
|
+
debug.log('release-loop.gate', 'executor', { version: opts.version, ...placement, requested: opts.executor ?? null });
|
|
1824
|
+
}
|
|
1531
1825
|
if (opts === 'help') {
|
|
1532
|
-
console.log('Usage: bun scripts/release-loop/gate-node.ts --commit <sha> --version <v> [--baseline-version <prev>] [--baseline-commit <sha>] [--remote <ssh-host>] [--remote-mirror <path>] [--pod-pool <pool>] [--pod-shards <n>] [--pod-shard-timeout-seconds <n>] [--fresh-shards] [--json]\nWithout flags, input.commit, input.version and input.previousVersion come from the JSON file at ELANOUS_GRAPH_CONTEXT.');
|
|
1533
|
-
|
|
1826
|
+
console.log('Usage: bun scripts/release-loop/gate-node.ts --commit <sha> --version <v> [--baseline-version <prev>] [--baseline-commit <sha>] [--remote <ssh-host>] [--remote-mirror <path>] [--pod-pool <pool>] [--pod-shards <n>] [--pod-shard-timeout-seconds <n>] [--fresh-shards] [--executor <pod|host>] [--json]\nWithout flags, input.commit, input.version and input.previousVersion come from the JSON file at ELANOUS_GRAPH_CONTEXT.\n--executor host (input.gateExecutor) ignores every pod option and sweeps on --remote (input.gateRemote, required).');
|
|
1827
|
+
return 0;
|
|
1534
1828
|
}
|
|
1535
1829
|
version = opts.version;
|
|
1536
|
-
result = await judgeGate(opts);
|
|
1830
|
+
result = await (runner ? judgeGate(opts, runner) : judgeGate(opts));
|
|
1537
1831
|
} catch (error) {
|
|
1538
|
-
result = { outcome: 'error', commit: '', introduced: [], preexisting: 0, fixed: 0, knownEnv: 0, knownEnvCleared: [], stalledEnv: [], durationMs: Date.now() - start, error: String(error) };
|
|
1832
|
+
result = { outcome: 'error', commit: '', introduced: [], preexisting: 0, fixed: 0, knownEnv: 0, knownEnvCleared: [], stalledEnv: [], rcNonzeroNoFailures: [], durationMs: Date.now() - start, error: String(error) };
|
|
1833
|
+
placement ??= requestedGateExecutor(argv, env);
|
|
1539
1834
|
}
|
|
1540
1835
|
if (result.error) console.error(result.error);
|
|
1541
|
-
debug.log('release-loop.gate', 'result', { version, outcome: result.outcome });
|
|
1542
|
-
emitNodeResult(graphGateResult(result, !!
|
|
1543
|
-
|
|
1836
|
+
debug.log('release-loop.gate', 'result', { version, outcome: result.outcome, ...(placement ?? {}) });
|
|
1837
|
+
emitNodeResult({ ...graphGateResult(result, !!env.ELANOUS_GRAPH_CONTEXT), ...(placement ?? {}) });
|
|
1838
|
+
return result.outcome === 'ok' ? 0 : result.outcome === 'regression' ? 1 : 2;
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
if (import.meta.main) {
|
|
1842
|
+
// The graph spawns this file as its own process, which inherits no daemon sink — without this every
|
|
1843
|
+
// `release-loop.gate`/`release.gate`/`pod.pool` event stayed in the file trail and `elanous logs` read 0 rows.
|
|
1844
|
+
await registerStandaloneLogSink('release-loop');
|
|
1845
|
+
process.exitCode = await runGateNode(process.argv.slice(2), process.env);
|
|
1544
1846
|
}
|