elanous 0.2.34 → 0.2.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/{abb4b98b.163e0f56e84ae35d.js → 053a429e.9a63436527cc751c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/109.f196abf3484bb97f.js +287 -0
- package/apps/pwa/out/_next/static/chunks/1201.afbfc5b1c5e5cbe4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1517.9275b89572098ff3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1574.ad11f8db8396ab45.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1614.7a8c5c40aee0261f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1871-f1eb29d8dbb4065b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/190.285c832bd9626f8c.js +64 -0
- package/apps/pwa/out/_next/static/chunks/2193.0ea24c4a7aa1d004.js +36 -0
- package/apps/pwa/out/_next/static/chunks/223-ad865c1e80491d78.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2265-f0d90b6dbec05f81.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2267.eb4a62a209f09ec4.js +149 -0
- package/apps/pwa/out/_next/static/chunks/2341-86cd06c0cf01aa33.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2352.9abc42422bade876.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2397-c1653a816061842d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2500.cbc1257b774aae52.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6086.913a69e6fa3e4a8b.js → 2683.c9773a0c589b031b.js} +4 -4
- package/apps/pwa/out/_next/static/chunks/2709.8e1889034255fc5f.js +24 -0
- package/apps/pwa/out/_next/static/chunks/{5576-82d3703636d5c4b9.js → 275-02ea4c2a33980422.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/{ffdc3a04.b9c23c1b28dd4a96.js → 2812ca6a.3b9f4a7a08885744.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/284.ac77c0db55bfd3a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2941.3c3546f96bc6efa7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2976.b000698a3c6512dc.js +24 -0
- package/apps/pwa/out/_next/static/chunks/{9550.d5f76828f972058c.js → 2986.45d7652e4f13557e.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/3031-85328a92fcce4da6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8817.61bb7ffbba502ecb.js → 3136.30aaaa5fa03662a3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{9131-fce7256c4180c4a5.js → 3148-98efd4869d660a87.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{3498.cf2a15a8a9e5b926.js → 3155.2587a55196ecb394.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/3277.785db1cf09f98601.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3320-f9c77381f85a2c0a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3103.84470c8445962f04.js → 3324.a09703033abcbd2f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3386-91049feabff9a94e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/339.80afb7e8839372af.js +8 -0
- package/apps/pwa/out/_next/static/chunks/3410.7f9af5ab77cbe65f.js +115 -0
- package/apps/pwa/out/_next/static/chunks/{3819.163d4777f0a597f6.js → 3538.a445e50bb359d068.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/3540c645.2595a6af01a97672.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3621.581b2054cc14bee4.js +286 -0
- package/apps/pwa/out/_next/static/chunks/3676.8c28d16fd84ca843.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3414.172ad4ca2d9b08e8.js → 3727.7949ccaacaa71242.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/375-b84540853523fe52.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3757-180308bc61021b05.js +1 -0
- package/apps/pwa/out/_next/static/chunks/379.ee262e37586fe1ae.js +74 -0
- package/apps/pwa/out/_next/static/chunks/3840-5825d56d46976a92.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3969-81054381eaec4c4c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9b48c871.204d5d13e7809bd0.js → 3a3d9966.cc4d4585a2708e7c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4067-bf9aa60547972937.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4116.a84a92a2492c080e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4176.e5729c93c29151eb.js +104 -0
- package/apps/pwa/out/_next/static/chunks/4227-e6c84686bc8946ca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6740.43de73fc427c9fe2.js → 4286.b70c4520306817e8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4304.e9b2512834a9e23e.js +23 -0
- package/apps/pwa/out/_next/static/chunks/4327.b9e08a71c0009348.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8548.036c344973968847.js → 4615.8525eb0add35eba6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4946.bd106eafd668f248.js +148 -0
- package/apps/pwa/out/_next/static/chunks/{3411-f87314c6c66ebc73.js → 5085-bce4904931767ca6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5125.2eb9968b83767193.js +93 -0
- package/apps/pwa/out/_next/static/chunks/5128-75b733fd74a9a1eb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3110.0a7065f25c841818.js → 528.38ce20ef8aa251ff.js} +16 -16
- package/apps/pwa/out/_next/static/chunks/5489f3fe.4bb1458b09f6dead.js +122 -0
- package/apps/pwa/out/_next/static/chunks/{1644.35dd25240ae24e4b.js → 552.b7d061ad91eeac51.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/5560-2a4648f6a30f16e7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3837.722e053fb9f321ad.js → 5711.957b095705c12560.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{2124.ed5bf480cea7f85b.js → 575.d929793a53f27c4b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5772-e29e14f8ff127122.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5887.5b2f5b44ce0f7e9a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5899-4c112a2d908cf97f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7562.948255cbc3e79d33.js → 599.c39c571b6733f2a2.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/6041.1b65efb796ebc2a5.js +79 -0
- package/apps/pwa/out/_next/static/chunks/6057-765c0a26028c86b2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6142.f289e0d039f453a6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6199-6bcd61d2a3910e04.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6236.cdf7293e710a9b2d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6387-237e079dac9a3ae6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6448-b6d272d074276e14.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6466-92e65a48490b68a9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6566.926a1d1b7c911442.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6580-cdc0e4a634ce4708.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6659-b242dc31b20b3f29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6690.c5d9b321b1f79384.js +1 -0
- package/apps/pwa/out/_next/static/chunks/671-e9b6ad0c771c5514.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6862.bcbf89dd0f4496b3.js +27 -0
- package/apps/pwa/out/_next/static/chunks/6912-7b375ee48732c767.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5446.edf141cff9ef647d.js → 6925.562d532110879886.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{7005.1623d2aea78eb85f.js → 6943.c6b24124853a47b2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/727.7deb87749b0a343a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8368.014caa8de2f5af34.js → 7481.39f7a4654907bf6e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7545.b1aa42cd15503319.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7551-416d298c2dc4a9ab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2554.3b5e200f5f5cfcd6.js → 771.c3cdb6c1f7f47c02.js} +16 -16
- package/apps/pwa/out/_next/static/chunks/7784.1f0ac4a16d190cf4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7870-309de463786ac1fc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7888-e29b151bb6a3bb8b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3121.779fafe3e44612c5.js → 7891.03f910124a737985.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7905-6ba78d9234608f79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8037.e254fd797aa224d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8207.f527886b7c464529.js +1 -0
- package/apps/pwa/out/_next/static/chunks/856.50bda61439d465e6.js +62 -0
- package/apps/pwa/out/_next/static/chunks/{9a8cb466.cc9fc9e6e46038aa.js → 873d6c12.09bd499f57171543.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{7431.3051e9b8a4372d2d.js → 8904.7d51af2dc033965c.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/9031-92e9b487edb681ee.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9060.5410d9f6a95f6287.js +90 -0
- package/apps/pwa/out/_next/static/chunks/914.8abefd48d579547b.js +94 -0
- package/apps/pwa/out/_next/static/chunks/9187-c87a2207d4ac5d46.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1895-60ed0e341c658bd4.js → 9250-072df22eaadaa6b5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9289.3e92f278821e18e0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9319.7816d3cc741cd34f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9327-e016bb9f1674d24f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9365.3de48bf75193ff9a.js +77 -0
- package/apps/pwa/out/_next/static/chunks/9383.5b5e9aa56b5fea97.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9387-cb929573ef436f39.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9489.c27becacc3283dec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2547.dd27303624cb4da8.js → 9514.131c52cc75fe6439.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9536.28962663c7734376.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9686-12286f5bbf15a5a0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9776.289d40cc5a4c342a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9789.60f723d18097ec30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/989.f0c17523c5e61f65.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9972-1df393aa8d4f7d52.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9f31e478.fefcbcd3c388ebc5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-96750aba7430959f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-4105e5bbbb043024.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5d4fa75c2acb113e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-a79a9bdb3b2142bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-f1c2047484992a35.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-15ffc49b2d8c70e2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-67d53a3fc8734135.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-e30c8dc696491336.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-2136e04fa77696e2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-fecd2814790c2e12.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-280aa4a98fc5bb44.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-c14e96b65fb4b883.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-187dac74825ed6bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-6bc7246ea8c92448.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/{page-4fb31eb476dcbad3.js → page-083747f1d4518f61.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-96750aba7430959f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-15ca95a16ec96e1f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-a8ac031b3810eb64.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-8835f93110494cda.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-e2de2e59111c9aad.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-f2b2e4bf6e981450.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-5b92b7f75df20948.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-468eddf2b1a84ecb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-2af16e62c12f22df.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-18b40867a77a5793.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-fe934f2979f5a4a2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-3eec435537357170.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ff88cbfd2aa99fc8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a5b39fb780c70ab5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-ae465dde1d1b3933.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-e7abc34a48a6f71e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-cab9674fde9ead26.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-1da49bc8712284d0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-5e8aa657551ac912.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-fcc36f36c6dc1fe2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-d22db22f2a7296ca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-d12dc7b1773878f0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-99debec9ea7fbec0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-96750aba7430959f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-2a088a78bad7e924.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-2e8ed57db7e69993.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-4b3a16213c2ae8fe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-f098747ddf38c98b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-c15958835534404b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-68279e3c843c1fd7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-a34a74453367a39d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-8767a7db2155a5bd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-bd5976683460d7f2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-b7364835e7f2ef54.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-311af16b3ee0ffca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-fb670757274ae990.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5014f970-22bef0b5210f7d60.js → ccae1c4b-0cf414f5eff1b10d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{4fa33ebd.21c1bd5b294496e6.js → ceba0382.650712b56b85b582.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/da5ecc5c-b005ad8cac737d57.js +1 -0
- package/apps/pwa/out/_next/static/chunks/f9a61e4d.1fc2c1dbdd49fe46.js +1 -0
- package/apps/pwa/out/_next/static/chunks/framework-6d497cbf1eef04c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-012f5edec932f337.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-da5aeb809edc94a2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-5ee319af6f749dfe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-4bc84653a718d6c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-8a96cf6775c73d6e.js +1 -0
- package/apps/pwa/out/_next/static/{L04y73dlwKeTW-GetaTF3 → uC-N0emedeNOFuaBQFv-I}/_buildManifest.js +1 -1
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +22 -22
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +22 -22
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +22 -22
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +21 -21
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +21 -21
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +22 -22
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +21 -21
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +22 -22
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +22 -22
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +21 -21
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +22 -22
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +22 -22
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +22 -22
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +22 -22
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +21 -21
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +21 -21
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +22 -22
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +22 -22
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +22 -22
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +21 -21
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +21 -21
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +21 -21
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +22 -22
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +22 -22
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +21 -21
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +21 -21
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +21 -21
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +21 -21
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +22 -22
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +22 -22
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +21 -21
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +22 -22
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +22 -22
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +22 -22
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +22 -22
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +22 -22
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +21 -21
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +22 -22
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +22 -22
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +22 -22
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +22 -22
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +22 -22
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +22 -22
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +22 -22
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +22 -22
- package/bin/elanous.mjs +3 -2
- package/catalog/providers/opengateway.yaml +25 -0
- package/docs/goal-context/01-goal-authoring-checks.md +8 -0
- package/docs/goal-context/03-verification-menu.md +24 -0
- package/docs/goal-context/04-mock-scenarios.md +100 -0
- package/docs/goal-context/05-authoring-lessons.jsonl +47 -0
- package/docs/goal-context/README.md +7 -0
- package/graphs/catalog/catalog.yaml +1 -1
- package/graphs/default-loop.yaml +63 -4
- package/graphs/document-loop.yaml +51 -3
- package/graphs/implement-loop.yaml +77 -4
- package/graphs/journey/ta-journey.yaml +11 -1
- package/graphs/journey/units/author-dialogue/author-dialogue.yaml +42 -0
- package/graphs/journey/units/author-dialogue/observe.ts +96 -0
- package/graphs/journey/units/author-dialogue/recipes.yaml +10 -0
- package/graphs/operate-loop.yaml +51 -3
- package/graphs/release/recipes.yaml +6 -0
- package/graphs/release/release-loop.yaml +17 -4
- package/graphs/research-loop.yaml +137 -3
- package/graphs/test-diet/test-diet.yaml +3 -0
- package/graphs/wizard/recipes.yaml +44 -0
- package/graphs/wizard/wizard-loop.yaml +44 -0
- package/graphs/work/research-report.yaml +2 -0
- package/package.json +1 -1
- package/scripts/ci-typecheck-changed.ts +139 -5
- package/scripts/coord-role-pack.ts +19 -6
- package/scripts/first10-gate.ts +416 -0
- package/scripts/fleet/wsl-node-setup.sh +432 -0
- package/scripts/hq/standby-snapshot.ts +17 -12
- package/scripts/release-loop/docs-land-node.ts +21 -5
- package/scripts/release-loop/docs-node.ts +204 -8
- package/scripts/release-loop/gate-baseline-receipt.ts +155 -0
- package/scripts/release-loop/gate-node.ts +427 -72
- package/scripts/release-loop/gate-partial.ts +65 -11
- package/scripts/release-loop/gate-radius-live.ts +177 -0
- package/scripts/release-loop/gate-radius-shadow.ts +1 -1
- package/scripts/release-loop/node-verdict.ts +14 -1
- package/scripts/release-loop/npm-publish-node.ts +66 -12
- package/scripts/release-loop/probe-pod-host-dirs-live.ts +5 -0
- package/scripts/release-loop/pwa-remote.ts +166 -0
- package/scripts/release-loop/pwa-scenarios-node.ts +19 -3
- package/scripts/release-loop/resume-cap.ts +14 -34
- package/scripts/release-loop/resume-release.ts +78 -15
- package/scripts/release-loop/traffic-node.ts +59 -0
- package/scripts/release-loop/traffic-transition.ts +159 -0
- package/scripts/release-loop/unattended-release.ts +2 -0
- package/scripts/rhythm/daily.ts +137 -3
- package/scripts/test-diet/lib.ts +6 -1
- package/scripts/test-diet/node.ts +194 -13
- package/scripts/ux-sim.ts +92 -0
- package/skills/README.md +1 -0
- package/src/acp/core-turn-bridge.ts +48 -6
- package/src/agent/delegate-hint.ts +16 -0
- package/src/agent/runner.ts +82 -2
- package/src/agent/self-ambient-cache.ts +8 -0
- package/src/agent/task-notification.ts +56 -10
- package/src/agent/types.ts +4 -0
- package/src/agent-substrate/acp-reviewer.ts +39 -4
- package/src/agent-substrate/pr-reviewer.ts +72 -3
- package/src/agent-substrate/review-intent.ts +29 -2
- package/src/agent-substrate/review-observation.ts +3 -0
- package/src/agent-substrate/self-review-cli.ts +229 -28
- package/src/autopilot/mission-codebase-gate.ts +141 -12
- package/src/autopilot/pr-manager.ts +240 -41
- package/src/boot/daemon-tools/self-implement.ts +1 -0
- package/src/budget/run-usage-rollup.ts +1 -0
- package/src/budget/usage-outcomes.ts +8 -4
- package/src/capture/tui-self-report.ts +102 -0
- package/src/chat/goal-continue-verdict.ts +41 -0
- package/src/chat/index.ts +30 -6
- package/src/chat/internal-turn-origin.ts +1 -0
- package/src/chat/paste-fold.ts +86 -0
- package/src/chat/pickers/modal-runtime.ts +10 -27
- package/src/chat/tool-render/block.ts +32 -0
- package/src/chat/tool-render/dispatch.ts +2 -20
- package/src/chat/tool-render/result-text.ts +98 -0
- package/src/chat/turn-typeahead.ts +70 -7
- package/src/cli/agent-cli.ts +13 -2
- package/src/cli/config-audit.ts +119 -0
- package/src/cli/doctor-cli.ts +26 -1
- package/src/cli/guardian-cli.ts +10 -0
- package/src/cli/herdr-cli.ts +34 -0
- package/src/cli/light-entry-route.ts +50 -7
- package/src/cli/log-event-names.ts +2 -0
- package/src/cli/make-cli.ts +247 -11
- package/src/cli/msg-cli.ts +117 -3
- package/src/cli/pod-cli.ts +77 -3
- package/src/cli/pr-cli.ts +34 -18
- package/src/cli/pr-land-base-read.ts +11 -2
- package/src/cli/pr-land-heavy-remote.ts +6 -3
- package/src/cli/pr-land-overlap-evidence.ts +23 -4
- package/src/cli/pr-land-pod-shadow-job.ts +3 -0
- package/src/cli/pr-land-recovery.ts +117 -1
- package/src/cli/pty-takeover-cli.ts +8 -7
- package/src/cli/release-checklist-read-cli.ts +79 -0
- package/src/cli/release-cli.ts +92 -106
- package/src/cli/release-single-flight.ts +1 -1
- package/src/cli/remote-resolve.ts +1 -1
- package/src/cli/root-tui-entry.ts +48 -0
- package/src/cli/sim-cli.ts +22 -0
- package/src/cli/tasks-card-show.ts +162 -0
- package/src/cli/tasks-cli.ts +1065 -338
- package/src/cli/tasks-read-cli.ts +197 -0
- package/src/cli/usage-cli.ts +13 -11
- package/src/clipboard/history.ts +179 -13
- package/src/config.ts +8 -1
- package/src/core-turn/run-core-turn.ts +4 -2
- package/src/core-turn/run-goal-loop.ts +73 -16
- package/src/core-turn/types.ts +6 -1
- package/src/dashboard/chat/acp-chat.ts +8 -0
- package/src/dashboard/context/keys.ts +8 -31
- package/src/dashboard/index.ts +280 -59
- package/src/dashboard/input/agent-wake.ts +7 -1
- package/src/dashboard/input/chat-main-plain-turn-runtime.ts +146 -3
- package/src/dashboard/input/chat-main-slash-command.ts +4 -2
- package/src/dashboard/input/goal-continue.ts +210 -0
- package/src/dashboard/input/goal-interim.ts +2 -0
- package/src/dashboard/input/internal-turn.ts +2 -8
- package/src/dashboard/input/seat-autowake.ts +9 -7
- package/src/dashboard/input/turn-autoheal.ts +52 -0
- package/src/dashboard/render/theme-resolver.ts +33 -4
- package/src/dashboard/seat-boot-preamble.ts +132 -5
- package/src/dashboard/slash-runtime/chat-persona.ts +65 -8
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +3 -2
- package/src/dashboard/slash-runtime/model-choices.ts +15 -0
- package/src/dashboard/stale-install-check.ts +3 -99
- package/src/debug/batch-queue.ts +119 -0
- package/src/debug/tui-log-tag.ts +67 -0
- package/src/display/agent-surface.ts +7 -0
- package/src/domains/schedule-registry.ts +16 -2
- package/src/domains/standalone-log-sink.ts +15 -2
- package/src/esc-abort-gate.ts +69 -0
- package/src/git-fs/idle-refresh.ts +76 -0
- package/src/git-fs/index.ts +18 -7
- package/src/git-fs/worktree.ts +137 -5
- package/src/graph-kinds/registry.ts +2 -2
- package/src/graph-runner/graph-cli.ts +18 -0
- package/src/graph-runner/runner.ts +173 -21
- package/src/graph-wizard/generate.ts +108 -4
- package/src/graph-wizard/steps.ts +15 -0
- package/src/grok/credential.ts +10 -3
- package/src/grounding/proactive-ground.ts +86 -0
- package/src/harness/control-inbox.ts +49 -1
- package/src/harness/harness-cli-command.ts +163 -35
- package/src/harness/harness-incidents.ts +85 -1
- package/src/harness/launch-utterance-ledger.ts +208 -0
- package/src/harness/pod-control-send.ts +33 -5
- package/src/harness/self-send-remote.ts +69 -0
- package/src/harness/self-send-routing-guard.ts +6 -0
- package/src/harness/self-send-target.ts +4 -3
- package/src/impl-discipline/detector.ts +16 -4
- package/src/impl-discipline/system-prompt.ts +11 -0
- package/src/impl-discipline/types.ts +2 -2
- package/src/index.ts +822 -154
- package/src/input-core/context-key-runtime.ts +21 -0
- package/src/intake-plane/already-built-hint.ts +4 -2
- package/src/intake-plane/requirement-funnel.ts +29 -1
- package/src/light-cli.ts +32 -11
- package/src/llm/opengateway-models.ts +11 -0
- package/src/llm/provider-credentials.ts +14 -0
- package/src/llm/provider-detect.ts +8 -1
- package/src/llm/runtime-model-family.ts +2 -1
- package/src/llm-wellformed.ts +70 -0
- package/src/llm.ts +247 -59
- package/src/loops/msg-wake.ts +35 -39
- package/src/loops/orchestrator/inbox.ts +59 -11
- package/src/loops/orchestrator/knowhow-pack.ts +364 -0
- package/src/loops/orchestrator/tick.ts +5 -1
- package/src/maturity/feature-maturity.ts +1 -1
- package/src/model-tier/llm-tier-map.ts +15 -1
- package/src/models/prompts.ts +17 -2
- package/src/msg/msg-post-office.ts +116 -0
- package/src/mss/logging/log-store.ts +79 -22
- package/src/native-tool-catalog.ts +15 -1
- package/src/nexus/api/decisions-route.ts +38 -18
- package/src/nexus/api/graph-wizard-api.ts +20 -2
- package/src/nexus/api/loop-resources.ts +13 -1
- package/src/nexus/boot/register-mcp-clients.ts +263 -38
- package/src/nexus/onboarding/first-boot-wizard.ts +56 -8
- package/src/oauth/codex.ts +7 -0
- package/src/onboarding/first-run.ts +37 -6
- package/src/onboarding/web-first.ts +43 -11
- package/src/ops/overview-collect.ts +30 -9
- package/src/ops/overview.ts +22 -9
- package/src/persona/loader.ts +11 -0
- package/src/persona/types.ts +8 -0
- package/src/pr-tool/census.ts +261 -27
- package/src/prompt-library/session-guidance.ts +14 -0
- package/src/provider-summary.ts +8 -0
- package/src/pty-shell/herdr-agent-report.ts +118 -9
- package/src/pty-shell/herdr-prompt.ts +266 -0
- package/src/pty-shell/pty-control-ipc.ts +151 -8
- package/src/pty-shell/pty-manifest.ts +7 -0
- package/src/pty-shell/pty-special-keys.ts +42 -2
- package/src/pty-shell/stale-install-check.ts +99 -0
- package/src/pty-shell/tui-run-state.ts +14 -2
- package/src/release-loop/auto-start.ts +20 -2
- package/src/release-loop/checklist.ts +100 -4
- package/src/release-loop/landed-but-yellow.ts +129 -5
- package/src/release-loop/light-rc.ts +1 -0
- package/src/release-loop/rehearsal.ts +37 -21
- package/src/repl/index.ts +192 -17
- package/src/repl/ops-msg-bridge.ts +32 -0
- package/src/seat-loop/knowhow-lint.ts +1 -1
- package/src/self-dev/ask-launch-flow.ts +44 -5
- package/src/self-dev/cli-help-text.ts +17 -0
- package/src/self-dev/decompose-proposal.ts +172 -20
- package/src/self-dev/decompose.ts +61 -0
- package/src/self-dev/dev-cli.ts +48 -12
- package/src/self-dev/dev-pipeline.ts +8 -2
- package/src/self-dev/draft-residue.ts +37 -1
- package/src/self-dev/draft-sweep.ts +137 -11
- package/src/self-dev/draft-triage-rules.ts +138 -0
- package/src/self-dev/fleet-reduce.ts +3 -2
- package/src/self-dev/glm-credential-guard.ts +77 -0
- package/src/self-dev/graph-front-nodes.ts +112 -0
- package/src/self-dev/graph-journey-nodes.ts +2 -0
- package/src/self-dev/graph-journey-route.ts +12 -2
- package/src/self-dev/harness-run-cli.ts +3 -15
- package/src/self-dev/orchestrate-cli-text.ts +10 -0
- package/src/self-dev/orchestrate-cli.ts +68 -3
- package/src/self-dev/orchestrate.ts +116 -17
- package/src/self-dev/review-provider-fallback.ts +35 -1
- package/src/self-dev/run-supervisor.ts +55 -2
- package/src/self-dev/run-types.ts +1 -1
- package/src/self-dev/self-orchestrate-runtime.ts +1 -11
- package/src/self-dev/shard-aggregate.ts +42 -5
- package/src/self-dev/shard-run.ts +8 -1
- package/src/self-health/autoheal.ts +153 -0
- package/src/self-health/signatures.ts +6 -0
- package/src/self-implement/blocked-draft-policy.ts +20 -0
- package/src/self-implement/classify-shadow.ts +3 -7
- package/src/self-implement/device-first.ts +116 -0
- package/src/self-implement/doctor-visit.ts +257 -2
- package/src/self-implement/evidence-command-budget.ts +80 -0
- package/src/self-implement/gate-baseline.ts +56 -17
- package/src/self-implement/gate-cli.ts +1 -0
- package/src/self-implement/gate-remote.ts +31 -20
- package/src/self-implement/goal-author-cli.ts +1 -0
- package/src/self-implement/goal-author-decompose.ts +534 -45
- package/src/self-implement/goal-author.ts +282 -7
- package/src/self-implement/goal-elaborate.ts +449 -0
- package/src/self-implement/goal-refine.ts +273 -0
- package/src/self-implement/graph-overlay-yaml.ts +14 -3
- package/src/self-implement/graph-variant.ts +40 -6
- package/src/self-implement/headless-elanous-driver.ts +19 -3
- package/src/self-implement/off-diff-evidence.ts +58 -6
- package/src/self-implement/orchestrator.ts +370 -62
- package/src/self-implement/pod-warm-pool-profile.ts +1 -0
- package/src/self-implement/pwa-import-graph.ts +1 -1
- package/src/self-implement/rework-budget-judgment.ts +5 -8
- package/src/self-implement/rework-policy.ts +30 -0
- package/src/self-implement/run-ledger.ts +108 -0
- package/src/self-implement/run-stop.ts +10 -1
- package/src/self-implement/run-ttl.ts +38 -24
- package/src/self-implement/running-runs.ts +29 -1
- package/src/self-implement/scope-adjacency.ts +115 -0
- package/src/self-implement/seams.ts +187 -28
- package/src/self-implement/stop-autoheal.ts +87 -1
- package/src/self-implement/wiring-check.ts +358 -0
- package/src/session-runtime/index.ts +1 -1
- package/src/setup/llm-env-detect.ts +5 -0
- package/src/shell-env-bootstrap.ts +11 -4
- package/src/skills/runner.ts +4 -2
- package/src/skills/tools/agent.ts +408 -40
- package/src/skills/tools/msg-ask.ts +100 -0
- package/src/skills/tools/ops-ledger.ts +2 -1
- package/src/skills/tools/schedule-wakeup.ts +23 -4
- package/src/skills/tools/shell.ts +61 -6
- package/src/skills/tools/terminal-modal-inject.ts +1 -2
- package/src/skills/tools/terminal-modal.ts +10 -8
- package/src/skills/url-router.ts +41 -1
- package/src/smart-wizard/run.ts +66 -0
- package/src/status/bar.ts +34 -6
- package/src/status/status-provider-info.ts +37 -6
- package/src/steward/hold-hygiene.ts +24 -6
- package/src/steward/rejudge-tick.ts +10 -2
- package/src/steward/ta-repair-event-collect.ts +17 -6
- package/src/steward/triage-budget.ts +194 -0
- package/src/steward/triage.ts +20 -3
- package/src/task-agent/actions.ts +72 -2
- package/src/task-agent/green-sweep.ts +4 -0
- package/src/task-agent/land-queue.ts +21 -8
- package/src/task-agent/live-moves.ts +182 -10
- package/src/task-agent/mission.ts +30 -0
- package/src/task-agent/parent-host.ts +123 -3
- package/src/task-agent/rejudge-on-head.ts +15 -0
- package/src/task-agent/repair-cycle.ts +138 -32
- package/src/task-agent/repair-next-move.ts +31 -3
- package/src/task-agent/repair-outcomes.ts +29 -0
- package/src/task-agent/repair-worker.ts +14 -4
- package/src/task-agent/shadow.ts +48 -4
- package/src/task-agent/task-card-prefix.ts +2 -0
- package/src/task-agent/task-hand.ts +525 -57
- package/src/task-agent/task-parent-role-llm.ts +36 -0
- package/src/task-agent/tier-split.ts +196 -0
- package/src/task-agent/work-order-build.ts +2 -0
- package/src/task-agent/work-order.ts +3 -2
- package/src/task-orchestrator/generator-prompt.ts +60 -11
- package/src/task-orchestrator/surfaces/pod-account-broker.ts +42 -0
- package/src/task-orchestrator/surfaces/pod-child-providers.ts +1 -1
- package/src/task-orchestrator/surfaces/pod-child-reuse.ts +146 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +130 -11
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +215 -3
- package/src/task-orchestrator/surfaces/pod-image-source.ts +89 -1
- package/src/task-orchestrator/surfaces/pod-launch-history.ts +133 -0
- package/src/task-orchestrator/surfaces/pod-lease.ts +17 -8
- package/src/task-orchestrator/surfaces/pod-manifest-test-normalize.ts +19 -2
- package/src/task-orchestrator/surfaces/pod-pool.ts +76 -4
- package/src/task-orchestrator/surfaces/pod-provenance.ts +82 -0
- package/src/task-orchestrator/surfaces/pod-repair-commit.ts +18 -3
- package/src/task-orchestrator/surfaces/pod-si-warm-entry.ts +16 -0
- package/src/task-orchestrator/surfaces/pod-si-warm-host.ts +224 -0
- package/src/task-orchestrator/surfaces/pod-si-warm-protocol.ts +201 -0
- package/src/task-orchestrator/surfaces/pod-si-warm-provider.ts +444 -0
- package/src/task-orchestrator/surfaces/pod-si-warm-runner.ts +387 -0
- package/src/task-orchestrator/surfaces/pod-si-warm-transport.ts +157 -0
- package/src/task-orchestrator/surfaces/pod-source-receive.ts +18 -0
- package/src/task-orchestrator/surfaces/pod-warm-provenance.ts +81 -0
- package/src/task-orchestrator/surfaces/pod-warm-worker-pod.ts +6 -2
- package/src/task-orchestrator/surfaces/pod-warm-worker-protocol.ts +7 -0
- package/src/task-orchestrator/surfaces/pod-warm-worker-provider.ts +521 -40
- package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +3 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +759 -50
- package/src/task-orchestrator/surfaces/self-implement.ts +65 -2
- package/src/tool-runtime/index.ts +15 -2
- package/src/tool-runtime/mcp-proxy-bootstrap.ts +29 -0
- package/src/tool-runtime/ops-ledger-runtimes.ts +10 -2
- package/src/tty-foreground.ts +155 -0
- package/src/tui/README.md +2 -0
- package/src/tui/chat/background-job-status.ts +54 -0
- package/src/tui/chat/goal-interim.ts +173 -0
- package/src/tui/chat/log-view.ts +267 -0
- package/src/tui/chat/self-health-status.ts +232 -0
- package/src/tui/entry.ts +695 -0
- package/src/tui/input/composer.ts +151 -0
- package/src/tui/seat-mail-boot.ts +59 -0
- package/src/tui/slash/compact-command.ts +7 -0
- package/src/tui/slash/compact.ts +94 -0
- package/src/tui/slash/new-session.ts +35 -0
- package/src/tui/slash/resume.ts +75 -0
- package/src/tui-client/local-chat-turn.ts +282 -0
- package/src/tui-client/local-harness-command.ts +63 -0
- package/src/tui-client/local-provider-command.ts +184 -0
- package/src/tui-client/next-dashboard-runtime.ts +8 -0
- package/src/tui-client/next-loop-command.ts +70 -0
- package/src/tui-client/next-wakeup.ts +191 -0
- package/src/tui.ts +37 -0
- package/src/ui/modal-adapter.ts +2 -2
- package/src/user-config.ts +4 -2
- package/src/ux-sim/classic-tui-seat-scenarios.ts +516 -0
- package/src/ux-sim/cli-config-audit-device.ts +121 -0
- package/src/ux-sim/dashboard-event-loop-stall-monitor-device.ts +238 -0
- package/src/ux-sim/event-monkey.ts +379 -0
- package/src/ux-sim/fake-bin.ts +87 -7
- package/src/ux-sim/fake-herdr.ts +238 -0
- package/src/ux-sim/fake-llm.ts +111 -6
- package/src/ux-sim/fault-glm-no-credential.ts +116 -0
- package/src/ux-sim/fault-nextmove-dispatch.ts +367 -0
- package/src/ux-sim/fault-pod-codex-503.ts +211 -0
- package/src/ux-sim/fault-scope-escape-lift.ts +216 -0
- package/src/ux-sim/fault-tui-400-autoheal.ts +297 -0
- package/src/ux-sim/goal-device.ts +619 -0
- package/src/ux-sim/in-process-cli.ts +22 -5
- package/src/ux-sim/mock-authoring-first-run-child.ts +35 -0
- package/src/ux-sim/mock-authoring-first-run.ts +97 -0
- package/src/ux-sim/mock-seat-pty-frame-child.ts +29 -0
- package/src/ux-sim/mock-seat-pty-frame.ts +297 -0
- package/src/ux-sim/mock-seat-pty-sequence-child.ts +21 -0
- package/src/ux-sim/mock-seat-pty-sequence.ts +206 -0
- package/src/ux-sim/next-tui-seat-scenarios.ts +631 -0
- package/src/ux-sim/onboarding-first-run-device.ts +269 -0
- package/src/ux-sim/pty-key-modifiers-device.ts +85 -0
- package/src/ux-sim/subagent-tools-device.ts +81 -0
- package/src/ux-sim/task-orchestrator-surfaces-self-implement-pod-device.ts +250 -0
- package/src/ux-sim/tty-foreground-steal-child.ts +21 -0
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/1162.c69abda138c7ce43.js +0 -79
- package/apps/pwa/out/_next/static/chunks/1449-1fede62af84c3e43.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1598-13fc845935e816df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1639.62c5e16fa20d242f.js +0 -94
- package/apps/pwa/out/_next/static/chunks/1661-adb8b2babc9aaf8c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2147.107c0fc6146fb15a.js +0 -23
- package/apps/pwa/out/_next/static/chunks/2202-ef1c9b000c3074fc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2247.1e326638ee43e672.js +0 -115
- package/apps/pwa/out/_next/static/chunks/2268.56e1e3d58180abae.js +0 -8
- package/apps/pwa/out/_next/static/chunks/2469-606819c812414021.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2593.e53c1620c0bb3599.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2690.e96671114222676a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2898-79ee1a5cc0cd9d13.js +0 -1
- package/apps/pwa/out/_next/static/chunks/298.26099a9f65e5c8ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2987-30cf9c2eb7b8bdd5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2992.51fc2a7295bd8139.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3001-5a52788c813b6113.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3014-291245fba9df7b5b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3068.ee78201f0251a18d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3083.cd399a17e9365f78.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3117-e1c474683fd1f824.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3251-2c17bd5a91f8de2e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/326.3fb32322bb2ddf6d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3421-7ff7fbaa156985a7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/381.f55dc63ea87735e6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4129-ab0a6189f1778633.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4361.93d14ba594b3a035.js +0 -104
- package/apps/pwa/out/_next/static/chunks/4364.7a7eb19cceb67d2f.js +0 -286
- package/apps/pwa/out/_next/static/chunks/4406.d71f506f7c3ee9d3.js +0 -149
- package/apps/pwa/out/_next/static/chunks/4461.2ad1514180ae7e29.js +0 -148
- package/apps/pwa/out/_next/static/chunks/4479.bd309cbe0e3842ee.js +0 -64
- package/apps/pwa/out/_next/static/chunks/4522-05966f777d66d13b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4543-9a3e9812e845de65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4557.f8f1d84a0d90d590.js +0 -74
- package/apps/pwa/out/_next/static/chunks/4589-79b11542179ef58c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4633-9f8f9f88ffbb8f5c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4831.23af4d10c0697b5e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4983.5951b85724f61f2a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5028-f0722e6114c313e7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5249.96a848656b582a93.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5311.16e0ffbfa5468b80.js +0 -62
- package/apps/pwa/out/_next/static/chunks/5489.ec2dd1b5272a19dc.js +0 -24
- package/apps/pwa/out/_next/static/chunks/559.df87695df637920c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5618-5c88f2f70178c4ad.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5764.05487bf0b73ddb6b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5930.e678826c3f831aab.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5dfb57f7.f2e3305326202474.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6053-d893974de92e6198.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6200-cefd6f33f59d765e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6314-edeba650ae686ac9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6316-bc9a9b74d7842d79.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6476-13b9779b73528008.js +0 -1
- package/apps/pwa/out/_next/static/chunks/666-04db3ac4c8e3c66f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/670.682e55106ef915a6.js +0 -77
- package/apps/pwa/out/_next/static/chunks/6816.b7ba9d0a1a4a46a7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/690-fbbcc777f189af37.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6989.0d3d4020e5cb3df6.js +0 -90
- package/apps/pwa/out/_next/static/chunks/7193-cf355cca5ba6c5d5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7217-d17393fe7af78fa6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7232.24edbd938a69c8d0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7295cd1e.b4b557908888eeee.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7323.adb02de21991883c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7386.5d8e7e42e818dfb6.js +0 -287
- package/apps/pwa/out/_next/static/chunks/7448.478958ffbbba0ac0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7537.ba2208c5c656b8a3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7596-962ae6db6cfc9c83.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7671.cff51beac212a2ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7883.3916734ee16d3e58.js +0 -27
- package/apps/pwa/out/_next/static/chunks/7952.b192c31b6a0e00cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/801.5b24e81eded22d1b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8176.8c9a8613f5a01ac1.js +0 -36
- package/apps/pwa/out/_next/static/chunks/819e78a6.fa3dd7f7da19bf61.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8220-78978450c3183d8d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8221-eb60b5bc692a3361.js +0 -1
- package/apps/pwa/out/_next/static/chunks/826.e9a26064f474949e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8459-a351487a4480f584.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8482-75823d513a4ce151.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8488.8da515248cda3f3a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8584.388a1779d9b203af.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8637.a55e098e61ffefb8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9207-8e6a12db30373f32.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9263-00ae932c62696a86.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9318.caafc3a130e8769c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/932.da3fb55951882562.js +0 -93
- package/apps/pwa/out/_next/static/chunks/9433.002f8426ac75b6b7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9464.dd631a5a14b1d660.js +0 -24
- package/apps/pwa/out/_next/static/chunks/9739.8d5f36ac67a3b236.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9743.beeabb9d5f86abe4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9843-a23d3f32dc43f715.js +0 -1
- package/apps/pwa/out/_next/static/chunks/994-28211077c7c587d5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-3b4c947f174e33d8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-369d5ce99483f859.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-63c0ed0eb26e5fce.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-62802c6003bc85cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-ecb41560d4e77be9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-7b65ad8130c93e95.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-8755477c15a9060c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-5725447dd972c471.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-b979de0591a69375.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-6d0796bf4633751a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-59c31dc0394e67b4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-12dfc95f6cc5c718.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-25788244e66aa4dd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-a2555dc705466322.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-3b4c947f174e33d8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-578729adbeaa40cb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-a1d20fffbec35205.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-4a558f94516b8004.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-9e3834705b86145e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-d7bff0f0905a662c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-cb1c5fe746ea4014.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-9ee320507356582a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66fd183b74835886.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-3272a064a01a9927.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-f281b7a7f6ef2529.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-d45a65f8b7124fcd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-e9c1fe684dd19a66.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-b8322932fc6e34de.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-82c66d9af2290575.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-63c6752009c97b05.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-bb33d42ebc358aec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-7e4c9379197d7214.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-38bf1e9a7001732b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-2e5b752012389d56.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-398c0faa173203f7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-002f53a965b6445c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-7f75997f61a26269.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-3b4c947f174e33d8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-05631753294f23fb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-edd700fd2cc3d7f8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-105f83ecb4594365.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-5823e87d2017f19c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-df3445b207ceceab.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-b51ca09566c03de7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-8aacd5ded5dbac60.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-bae3365a0b3c6a8e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-9ed52b48ff1aa7fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-b6f8cad35fe00946.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-6d25ceda500d2457.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-b6401a5f2c962fe8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/ebf6cf5e.0d2f887b040e80fb.js +0 -122
- package/apps/pwa/out/_next/static/chunks/f09ec7d7-d29abe23edcbb193.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-9322cb8a56ac081c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-2fbab294e49a1561.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-f36729f56621a8f3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-2a62e450b47c6114.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-5e17f7b445f34d43.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-b4263e2190ea7adf.js +0 -1
- /package/apps/pwa/out/_next/static/{L04y73dlwKeTW-GetaTF3 → uC-N0emedeNOFuaBQFv-I}/_ssgManifest.js +0 -0
|
@@ -4,7 +4,7 @@ import { appendFileSync, chmodSync, copyFileSync, existsSync, mkdirSync, mkdtemp
|
|
|
4
4
|
import { hostname, tmpdir } from 'node:os';
|
|
5
5
|
import { createHash, randomUUID } from 'node:crypto';
|
|
6
6
|
import { dirname, join, resolve } from 'node:path';
|
|
7
|
-
import { debug } from '../../src/debug/log.js';
|
|
7
|
+
import { debug, redactSecretText } from '../../src/debug/log.js';
|
|
8
8
|
import { registerStandaloneLogSink } from '../../src/domains/standalone-log-sink.js';
|
|
9
9
|
import { landingFreezeMessage, readLandingFreeze } from '../../src/release-loop/landing-freeze.js';
|
|
10
10
|
import { effectiveInstanceRoot, releaseLedgerRoot } from '../../src/instance/resolve.js';
|
|
@@ -12,9 +12,14 @@ import { getElanousConfigDirOverride } from '../../src/elanous-config-dir.js';
|
|
|
12
12
|
import { diffFailures, junitFailures, parseFailures, parseTimedOutFailures } from './gate-diff';
|
|
13
13
|
import { loadEnvKnownFailures, splitKnownEnv } from './env-known-failures';
|
|
14
14
|
import { loadGateBatchSafe } from './gate-batch-safe';
|
|
15
|
+
import { gateHistory } from './gate-history.js';
|
|
15
16
|
import { baselineFromCutLogs } from './baseline-from-logs';
|
|
16
|
-
import { mergePartialFailures,
|
|
17
|
+
import { mergePartialFailures, readPartialPlanResult, testFileOfId, verifyDocsOnlyCarry } from './gate-partial';
|
|
17
18
|
import { planGateRadiusShadow, shadowActual, writeGateRadiusShadow, type GateRadiusShadowPlan } from './gate-radius-shadow';
|
|
19
|
+
import { planHardRehearsalRadius, partialGateImagesMatch, preflightGateImageDigest } from './gate-radius-live';
|
|
20
|
+
import { actualGatePodImageDigest, gateBaselineReceipt, gateEnvFingerprint, gateGraphPins, gateSourcePins,
|
|
21
|
+
POD_SWEEP_INTEGRATION_ONLY } from './gate-baseline-receipt';
|
|
22
|
+
export { POD_SWEEP_INTEGRATION_ONLY } from './gate-baseline-receipt';
|
|
18
23
|
import { logPlanWalltime, planDurations, readFileMemory } from './shard-plan';
|
|
19
24
|
import { defaultGateHostMirrorReaders, resolveGateHostMirror, type GateHostMirror, type GateHostMirrorReaders } from './gate-host-mirror';
|
|
20
25
|
import { isGateSpawnLaneFile, planGateSpawnSeparatedShards } from './spawn-lane';
|
|
@@ -67,6 +72,8 @@ export function warmCommandMatchesRequest(command: WarmWorkerCommand, request: O
|
|
|
67
72
|
}
|
|
68
73
|
|
|
69
74
|
interface CommandResult { rc: number; output: string; passedIds?: string[]; stalledEnv?: Array<{ file: string; reason: 'no-output' }>; timedOut?: boolean;
|
|
75
|
+
/** Identity and complete selected inventory of this Pod sweep; only the producer may issue it. */
|
|
76
|
+
podSweep?: { id: string; shards: number; files: string[] };
|
|
70
77
|
/** Complete, source-bound one-file Job result for shadow comparison; absent means unmeasured. */
|
|
71
78
|
warmReference?: WarmWorkerFileResult | null;
|
|
72
79
|
warmAttemptId?: string;
|
|
@@ -74,7 +81,35 @@ interface CommandResult { rc: number; output: string; passedIds?: string[]; stal
|
|
|
74
81
|
/** GATE-POD-STALL-FAILFAST ① — shard Pods whose tests the mid-run watch aborted in this sweep. */
|
|
75
82
|
abortedShards?: number;
|
|
76
83
|
/** Each contributing Pod leaf already passed failuresOf; repeated printed ids cannot add new failures. */
|
|
77
|
-
validatedShards?: boolean
|
|
84
|
+
validatedShards?: boolean
|
|
85
|
+
/** GATE-RISK-LANE — the watch-list shard this sweep ran first, with its plan and measurement (observation only). */
|
|
86
|
+
riskLane?: GateRiskLaneRecord }
|
|
87
|
+
function redactedGateLogTail(input: string): string {
|
|
88
|
+
// Redact before trimming: a truncated token might no longer match the redaction rule.
|
|
89
|
+
const bytes = Buffer.from(redactSecretText(input), 'utf8');
|
|
90
|
+
let start = Math.max(0, bytes.length - 16_384);
|
|
91
|
+
while (start < bytes.length && (bytes[start]! & 0xc0) === 0x80) start++;
|
|
92
|
+
return bytes.subarray(start).toString('utf8');
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* GATE-FLOW-SIM — the gate's outside world as one seam: a scenario hands in the repo's fake clock
|
|
96
|
+
* (`src/ux-sim/fake-clock.ts`) plus fake Pod/kubectl/ssh tools, and the production entry hands in nothing.
|
|
97
|
+
* Every field falls back to the real tool, so an absent seam is today's behaviour byte for byte.
|
|
98
|
+
*/
|
|
99
|
+
export interface GateTools {
|
|
100
|
+
/** Wall clock the gate measures with; absent = `Date.now`. */
|
|
101
|
+
now?: () => number;
|
|
102
|
+
/** The Pod Job runner (the fake Pod stage of a scenario); absent = the positional `podCommand`, else `runPodCommand`. */
|
|
103
|
+
podCommand?: (options: RunPodCommandOptions) => Promise<PodCommandResult>;
|
|
104
|
+
/** Kubernetes client (pool readiness · Pod lifecycle · Job log tail · failure evidence); absent = `defaultKubectl`. */
|
|
105
|
+
kubectl?: typeof defaultKubectl;
|
|
106
|
+
/** SSH transport behind {@link GateRunner.command} on a remote host; absent = a real `ssh` child. */
|
|
107
|
+
ssh?: (host: string, script: string, limitMs?: number) => { rc: number; output: string; timedOut?: boolean };
|
|
108
|
+
/** SSH reachability probe of a multi-host roster; absent = `defaultRemoteRun`. */
|
|
109
|
+
remoteRun?: typeof defaultRemoteRun;
|
|
110
|
+
/** Pool admission a shard waits on (leases ⊕ a cluster reading); absent = the gate's own pool admission. */
|
|
111
|
+
admission?: GateAdmission;
|
|
112
|
+
}
|
|
78
113
|
export interface GateRunner {
|
|
79
114
|
command(cmd: string, args: string[], cwd: string, limitMs?: number): Promise<CommandResult>;
|
|
80
115
|
localCommand(cmd: string, args: string[], cwd: string, limitMs?: number): Promise<CommandResult>;
|
|
@@ -90,9 +125,15 @@ export interface GateRunner {
|
|
|
90
125
|
removeSnapshot(tree: string): Promise<void>;
|
|
91
126
|
/** Absolute remote bare mirror path, set before preparing the remote trees. */
|
|
92
127
|
remoteMirror?: string;
|
|
128
|
+
/** GATE-FLOW-SIM — the clock this runner measures with (the injected fake clock in a scenario); absent = `Date.now`. */
|
|
129
|
+
now?(): number;
|
|
93
130
|
}
|
|
94
131
|
export interface GateOptions {
|
|
95
132
|
commit: string;
|
|
133
|
+
/** The graph run that launched this gate; direct hard rehearsal instead pins source bytes explicitly. */
|
|
134
|
+
graphRunId?: string;
|
|
135
|
+
/** Only a direct hard rehearsal may pin graph bytes from its measured Git commit. */
|
|
136
|
+
rehearsalBaseline?: boolean;
|
|
96
137
|
forceFreeze?: boolean;
|
|
97
138
|
version: string;
|
|
98
139
|
baselineVersion?: string;
|
|
@@ -113,6 +154,8 @@ export interface GateOptions {
|
|
|
113
154
|
/** Opt-in gate shadow: provider fills the fenced lease identity. The Job remains authoritative. */
|
|
114
155
|
warmReplay?: (input: Omit<WarmWorkerCommand, 'leaseId' | 'generation' | 'workerId' | 'key'>) => Promise<{ command: WarmWorkerCommand | null; transport: WarmPodTransportResult }>;
|
|
115
156
|
warmQuarantine?: (command: WarmWorkerCommand, reason: string) => Promise<void>;
|
|
157
|
+
/** Explicit gate-profile producer handoff; shadow-only callers keep their Job path. */
|
|
158
|
+
warmProducerEnabled?: boolean;
|
|
116
159
|
warmFinalize?: (command: WarmWorkerCommand) => Promise<void>;
|
|
117
160
|
/** Set only on the cut recheck; baseline Jobs never consume the cut worker. */
|
|
118
161
|
warmCutSha?: string;
|
|
@@ -154,6 +197,9 @@ export interface GateOptions {
|
|
|
154
197
|
liveLog?: (job: { name: string; context?: string }) => string | null;
|
|
155
198
|
/** false = the stop did not take; the watch asks again every poll until it does (void = taken). */
|
|
156
199
|
stopJob?: (job: { name: string; context?: string }) => boolean | void;
|
|
200
|
+
/** Failure-only Pod status and log reader; injected tests never call the live cluster. */
|
|
201
|
+
failureEvidence?: (job: { name: string; context?: string }) => { podName?: string; termination?: { reason?: string; message?: string; exitCode?: number; finishedAt?: string }; logTail?: string; error?: string };
|
|
202
|
+
failureKubectl?: typeof defaultKubectl;
|
|
157
203
|
/** How often the stall watch reads the live log (ms); omitted = {@link GATE_POD_STALL_POLL_MS}. */
|
|
158
204
|
stallPollMs?: number;
|
|
159
205
|
/** GATE-POD-STALL-FAILFAST ① — seconds a shard may go without its part logs growing once test output began, before
|
|
@@ -165,6 +211,8 @@ export interface GateOptions {
|
|
|
165
211
|
abortTests?: (job: { name: string; context?: string }, reason: string) => boolean | void;
|
|
166
212
|
/** GATE-LIVE-OBS — the shard state file (`gateShardsPath(version)`) this sweep keeps current; absent = not written. */
|
|
167
213
|
shardsFile?: { path: string; version: string };
|
|
214
|
+
/** GATE-RISK-LANE — the ledgers `gateHistory` reads its watch list from; absent = no risk lane (the sweep plans as before). */
|
|
215
|
+
riskWatch?: { instanceRoots: readonly string[]; ledgerRoots: readonly string[] };
|
|
168
216
|
/** GATE-MULTI-HOST — is this pool member reachable (kubectl ns ⊕ ssh)? Injected by tests; absent = the real probe on
|
|
169
217
|
* the gate's own pool · an injected pool without it never marks a member down (unchanged). */
|
|
170
218
|
probeMember?: (member: PodPoolMember) => boolean;
|
|
@@ -182,7 +230,7 @@ export function withWarmGateHooks(opts: GateOptions, config: unknown, injectedRu
|
|
|
182
230
|
&& config.pod && typeof config.pod === 'object' && 'warmReuse' in config.pod
|
|
183
231
|
? config.pod.warmReuse as WarmReuseSwitchConfig : undefined;
|
|
184
232
|
return { ...opts, pod: { ...opts.pod, warmReplay: requestWarmPodReplay,
|
|
185
|
-
warmFinalize: finalizeWarmPodReplay, warmQuarantine: quarantineWarmPodReplay, warmReuseConfig } };
|
|
233
|
+
warmFinalize: finalizeWarmPodReplay, warmQuarantine: quarantineWarmPodReplay, warmProducerEnabled: true, warmReuseConfig } };
|
|
186
234
|
}
|
|
187
235
|
export interface GateResult {
|
|
188
236
|
outcome: 'ok' | 'regression' | 'error';
|
|
@@ -218,6 +266,10 @@ export interface GateResult {
|
|
|
218
266
|
/** GATE-POD-STALL-FAILFAST — the shard (and its one fresh retry) printed no test output within the stall window: unmeasured. */
|
|
219
267
|
stall?: { seconds: number; sinceCheckoutMs: number } }>;
|
|
220
268
|
partialSummary?: { pass: number; fail: number; errors: number; ran: number; files: number };
|
|
269
|
+
/** Number of complete shards retained when an incomplete sweep is resumed. */
|
|
270
|
+
preservedShardCount?: number;
|
|
271
|
+
/** Completed shard measurements retained when another shard stalled, so same-commit resume can reuse them safely. */
|
|
272
|
+
partialFailures?: SweepFailures;
|
|
221
273
|
/** GATE-PARTIAL — this verdict re-ran only these files and carried the prior gate's results for the rest. */
|
|
222
274
|
partial?: { priorCommit: string; files: number };
|
|
223
275
|
/** GATE-INTRO-RECHECK — new failures that were only the «timed out» shape and passed one isolated re-run with a
|
|
@@ -507,6 +559,71 @@ export function shardParts(paths: readonly string[], seconds: (file: string) =>
|
|
|
507
559
|
if (current.length) parts.push(current);
|
|
508
560
|
return parts;
|
|
509
561
|
}
|
|
562
|
+
/** GATE-RISK-LANE — the watched files this sweep ran first, and how that shard was planned and measured. */
|
|
563
|
+
export interface GateRiskLaneRecord { files: string[]; plannedSeconds: number; durationMs: number; rc: 0 | 1 | null }
|
|
564
|
+
|
|
565
|
+
/** GATE-RISK-LANE — the shard plan plus the risk shard it may have placed first. */
|
|
566
|
+
export interface GateRiskLanePlan {
|
|
567
|
+
/** Execution order: the risk shard first (absent when nothing is watched), then the current plan unchanged. */
|
|
568
|
+
shards: Array<{ files: string[]; plannedSeconds: number }>;
|
|
569
|
+
/** Assignable files this plan treated as risk — exactly `shards[0].files`; empty means «the current plan». */
|
|
570
|
+
riskFiles: string[];
|
|
571
|
+
riskPlannedSeconds: number;
|
|
572
|
+
spawnFiles: string[];
|
|
573
|
+
spawnShardCount: number;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* GATE-RISK-LANE — the watch list the gate-history table already keeps (`gateHistory().watchList`: introduced or flaky
|
|
578
|
+
* in at least two of the last three releases). Reading it never blocks a gate: an unreadable ledger set is «no list».
|
|
579
|
+
*/
|
|
580
|
+
export function readGateRiskWatchList(o: { instanceRoots: readonly string[]; ledgerRoots: readonly string[] }): string[] {
|
|
581
|
+
try { return gateHistory({ instanceRoots: o.instanceRoots, ledgerRoots: o.ledgerRoots }).watchList; }
|
|
582
|
+
catch (error) {
|
|
583
|
+
debug.log('release-loop.gate', 'risk-lane-watch-unavailable', { reason: String(error) });
|
|
584
|
+
return [];
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* GATE-RISK-LANE (0.2.36) — the next full sweep runs the files the watch list names in its first shard, so a repeat
|
|
590
|
+
* regression breaks before the general shards spend their deadline. Only the order changes:
|
|
591
|
+
* - a watched heavy file already runs alone at the isolation limit, and a watched spawn file keeps its own lane (a part
|
|
592
|
+
* mixing real child/PTY files with regular ones is refused by {@link gateShardShell}) — neither joins the risk shard;
|
|
593
|
+
* - every assignable file is assigned exactly once: the risk files leave the lane they would have been balanced into;
|
|
594
|
+
* - with no watch list, or one that names no file of this sweep, the plan is today's plan byte for byte;
|
|
595
|
+
* - when the watch list hits, the sweep runs exactly one more shard than configured (the risk shard in front), which is
|
|
596
|
+
* the same trade the configuration already makes for spawning a Pool Job.
|
|
597
|
+
*/
|
|
598
|
+
export function planGateRiskLaneShards(o: { assignable: readonly string[]; durations: Map<string, number>; shardCount: number;
|
|
599
|
+
heavy: ReadonlySet<string>; watchList: readonly string[] }): GateRiskLanePlan {
|
|
600
|
+
const light = o.assignable.filter((file) => !o.heavy.has(file));
|
|
601
|
+
const watched = new Set(o.watchList);
|
|
602
|
+
const riskFiles = [...new Set(light)].filter((file) => watched.has(file) && !isGateSpawnLaneFile(file)).sort();
|
|
603
|
+
const heavyShards = [...o.heavy].map((file) => ({ files: [file], plannedSeconds: o.durations.get(file) ?? 0 }));
|
|
604
|
+
const planWith = (files: readonly string[]) => planGateSpawnSeparatedShards(files, o.durations, o.shardCount);
|
|
605
|
+
if (!riskFiles.length) {
|
|
606
|
+
const current = planWith(light);
|
|
607
|
+
return { shards: [...current.shards, ...heavyShards], riskFiles: [], riskPlannedSeconds: 0,
|
|
608
|
+
spawnFiles: current.spawnFiles, spawnShardCount: current.spawnShardCount };
|
|
609
|
+
}
|
|
610
|
+
const risk = new Set(riskFiles);
|
|
611
|
+
const rest = planWith(light.filter((file) => !risk.has(file)));
|
|
612
|
+
// An unmeasured file takes the same median weight planShards gives it, so the shard's plan stays comparable.
|
|
613
|
+
const known = o.assignable.map((file) => o.durations.get(file))
|
|
614
|
+
.filter((value): value is number => value !== undefined && Number.isFinite(value) && value >= 0).sort((a, b) => a - b);
|
|
615
|
+
const median = known.length ? (known[Math.floor((known.length - 1) / 2)]! + known[Math.floor(known.length / 2)]!) / 2 : 1;
|
|
616
|
+
const plannedSeconds = riskFiles.reduce((sum, file) => {
|
|
617
|
+
const value = o.durations.get(file);
|
|
618
|
+
return sum + (value !== undefined && Number.isFinite(value) && value >= 0 ? value : median);
|
|
619
|
+
}, 0);
|
|
620
|
+
return {
|
|
621
|
+
shards: [{ files: [...riskFiles], plannedSeconds }, ...rest.shards, ...heavyShards],
|
|
622
|
+
riskFiles: [...riskFiles], riskPlannedSeconds: plannedSeconds,
|
|
623
|
+
spawnFiles: rest.spawnFiles, spawnShardCount: rest.spawnShardCount,
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
|
|
510
627
|
/**
|
|
511
628
|
* GATE-ENV-BASELINE (W5 · 10-15) — the gate shard Pod's deterministic test env, exported before anything runs.
|
|
512
629
|
* 0.2.29 gate failures were the Pod's «environment», not the code: no LLM provider · `man` stubbed · CLI spawn timeouts.
|
|
@@ -530,7 +647,8 @@ export function gateEnvBaselineLine(env: Readonly<Record<string, string>> = gate
|
|
|
530
647
|
* `shard.timeout` so the artifact reader does not mistake an unfinished child for a test verdict.
|
|
531
648
|
* A part without `.rc` did not finish. The shell itself always exits 0 (as before).
|
|
532
649
|
*/
|
|
533
|
-
export function gateShardShell(o: { parts: readonly (readonly string[])[]; cdpPatterns: readonly string[]; softSeconds: number; cachePrefix: string; installSlots: number; depsDir?: string; beatSeconds?: number; parallelParts?: number; testTimeoutMs?: number; partLimitSeconds?: number; sourceBinding?: { cutSha: string; attemptId: string } }): string {
|
|
650
|
+
export function gateShardShell(o: { parts: readonly (readonly string[])[]; cdpPatterns: readonly string[]; softSeconds: number; cachePrefix: string; installSlots: number; depsDir?: string; beatSeconds?: number; parallelParts?: number; testTimeoutMs?: number; partLimitSeconds?: number; rulesBaseSha?: string; sourceBinding?: { cutSha: string; attemptId: string } }): string {
|
|
651
|
+
if (o.rulesBaseSha !== undefined && !/^[0-9a-f]{40}$/.test(o.rulesBaseSha)) throw new Error('invalid rules gate base SHA');
|
|
534
652
|
// The planner keeps spawn files in their own shards; reject a mixed part rather than silently lose its limit.
|
|
535
653
|
for (const part of o.parts) {
|
|
536
654
|
if (part.some(isGateSpawnLaneFile) && !part.every(isGateSpawnLaneFile)) throw new Error('gate spawn lane part mixed with regular tests');
|
|
@@ -573,6 +691,7 @@ export function gateShardShell(o: { parts: readonly (readonly string[])[]; cdpPa
|
|
|
573
691
|
...(o.beatSeconds ? gateBeatScript(o.beatSeconds, o.installSlots > 0) : []),
|
|
574
692
|
...(o.cachePrefix ? [o.cachePrefix] : []),
|
|
575
693
|
gateEnvBaselineLine(),
|
|
694
|
+
...(o.rulesBaseSha ? [`export ELANOUS_GATE_BASE_SHA=${quote(o.rulesBaseSha)}`] : []),
|
|
576
695
|
// Seconds since this container's PID 1 started (the clone ran before this shell) — 0 when /proc cannot tell.
|
|
577
696
|
`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)`,
|
|
578
697
|
'case "$pod_age" in ""|*[!0-9]*) pod_age=0;; esac',
|
|
@@ -971,7 +1090,7 @@ export function readShardArtifacts(dir: string, parts: readonly (readonly string
|
|
|
971
1090
|
export const gateUnfinishedHint = (relativePath: string, bytes: number) => `[gate] unfinished: ${relativePath} · ${bytes}B (진행 중 part 출력은 그 파일)`;
|
|
972
1091
|
|
|
973
1092
|
// review-model-ab: Pod 에서 출력 없이 멈춤 2/2(10-04 0.2.11 · 로컬 단독 4/0 · 1.7s) — GATE-STALL 수리 전까지 Pod 밖.
|
|
974
|
-
|
|
1093
|
+
// The shared exclusion list lives in gate-baseline-receipt so radius verification uses the producer's rule.
|
|
975
1094
|
/** Nightly-only whole-repository cases; gate runs the fixture cases in these same files. */
|
|
976
1095
|
export const GATE_NIGHTLY_AUDITS: readonly string[] = [
|
|
977
1096
|
// Replaces F12's real-repo bucket B apps/pwa scope check and its whole-repo export partition check.
|
|
@@ -1132,7 +1251,8 @@ export function readIsolatedRun(run: CommandResult, label: string, file: string)
|
|
|
1132
1251
|
}
|
|
1133
1252
|
|
|
1134
1253
|
class StalledPodShards extends Error {
|
|
1135
|
-
constructor(readonly stalledShards: NonNullable<GateResult['stalledShards']>, readonly partialSummary: NonNullable<GateResult['partialSummary']>,
|
|
1254
|
+
constructor(readonly stalledShards: NonNullable<GateResult['stalledShards']>, readonly partialSummary: NonNullable<GateResult['partialSummary']>,
|
|
1255
|
+
readonly partialFailures: SweepFailures, readonly preservedShardCount: number, readonly abortedShards = 0) {
|
|
1136
1256
|
const unmeasured = stalledShards.filter((item) => item.stall);
|
|
1137
1257
|
const exitCode = stalledShards.filter((item) => item.reason === 'exit-code');
|
|
1138
1258
|
super(`pod sweep incomplete: stalled shards ${stalledShards.map(({ shard }) => shard).join(', ')}`
|
|
@@ -1142,6 +1262,23 @@ class StalledPodShards extends Error {
|
|
|
1142
1262
|
}
|
|
1143
1263
|
}
|
|
1144
1264
|
|
|
1265
|
+
/** Read completed shard failures without letting one malformed shard hide the stalled-shard verdict. */
|
|
1266
|
+
export function partialFailuresOf(runs: readonly (CommandResult & { files: number })[]): SweepFailures {
|
|
1267
|
+
return runs.reduce<SweepFailures>((sum, run) => {
|
|
1268
|
+
try {
|
|
1269
|
+
const parsed = failuresOf({ rc: run.rc, output: run.output, validatedShards: true }, `pod partial shard ${run.files}`);
|
|
1270
|
+
sum.failures.push(...parsed.failures);
|
|
1271
|
+
sum.errors.push(...parsed.errors);
|
|
1272
|
+
} catch (error) {
|
|
1273
|
+
debug.log('release-loop.gate', 'pod-partial-failures-unreadable', {
|
|
1274
|
+
files: run.files,
|
|
1275
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
1276
|
+
}, { level: 'warn' });
|
|
1277
|
+
}
|
|
1278
|
+
return sum;
|
|
1279
|
+
}, { failures: [], errors: [] });
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1145
1282
|
class WarmRecheckIncomplete extends Error {
|
|
1146
1283
|
constructor(message: string, readonly file: string, readonly shadow: WarmShadowObservation) { super(message); }
|
|
1147
1284
|
}
|
|
@@ -1225,9 +1362,9 @@ export const gateAdmissionWaitText = (reading: string | null) => `메모리 여
|
|
|
1225
1362
|
* the shard runs again on the others — one dead host no longer stops the gate. One-member pools keep the old behaviour.
|
|
1226
1363
|
*/
|
|
1227
1364
|
export const GATE_MEMBER_UNREADABLE_SECONDS_DEFAULT = 600;
|
|
1228
|
-
export function defaultGateMemberProbe(member: PodPoolMember): boolean {
|
|
1229
|
-
if (!checkPodPool([member], defaultKubectl).ok) return false;
|
|
1230
|
-
return !member.sshHost || defaultRemoteRun(member.sshHost, 'true').status === 0;
|
|
1365
|
+
export function defaultGateMemberProbe(member: PodPoolMember, tools: GateTools = {}): boolean {
|
|
1366
|
+
if (!checkPodPool([member], tools.kubectl ?? defaultKubectl).ok) return false;
|
|
1367
|
+
return !member.sshHost || (tools.remoteRun ?? defaultRemoteRun)(member.sshHost, 'true').status === 0;
|
|
1231
1368
|
}
|
|
1232
1369
|
|
|
1233
1370
|
export function poolGateAdmission(pool: PodPoolScheduler): GateAdmission {
|
|
@@ -1261,8 +1398,27 @@ export async function admitGateShard(admission: GateAdmission | undefined, o: {
|
|
|
1261
1398
|
}
|
|
1262
1399
|
}
|
|
1263
1400
|
|
|
1264
|
-
|
|
1401
|
+
/**
|
|
1402
|
+
* The real `ssh` transport behind {@link GateRunner.command} on a remote host. A scenario replaces it through
|
|
1403
|
+
* {@link GateTools.ssh} so the gate flow can run without any remote call.
|
|
1404
|
+
*/
|
|
1405
|
+
export function defaultSshCommand(host: string, script: string, limitMs?: number): { rc: number; output: string; timedOut?: boolean } {
|
|
1406
|
+
// Start in /tmp, not the caller checkout. macOS resolves that symlink to /private/tmp before the child sees it;
|
|
1407
|
+
// the remote command still `cd`s to the quoted path, so either spelling is the same directory.
|
|
1408
|
+
const run = spawnSync('ssh', [host, script],
|
|
1409
|
+
{ cwd: '/tmp', encoding: 'utf8', maxBuffer: 128 * 1024 * 1024, ...(limitMs ? { timeout: limitMs + 60_000, killSignal: 'SIGKILL' as const } : {}) });
|
|
1410
|
+
const timedOut = !!limitMs && (run.status === 124 || run.status === 137 || (run.error as NodeJS.ErrnoException | undefined)?.code === 'ETIMEDOUT');
|
|
1411
|
+
return { rc: run.status ?? 2, output: `${run.stdout ?? ''}\n${run.stderr ?? ''}${run.error ? `\n${run.error}` : ''}`, ...(timedOut ? { timedOut } : {}) };
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
export function createGateRunner(repo: string, remote?: string, commandOverride?: GateRunner['command'], podCommand: (options: RunPodCommandOptions) => Promise<PodCommandResult> = runPodCommand, poolOverride?: PodPoolScheduler, podLogTail?: (job: PodCommandResult) => string, tools: GateTools = {}): GateRunner {
|
|
1265
1415
|
let remoteMirror: string | undefined;
|
|
1416
|
+
// GATE-FLOW-SIM: the one place the real outside world is named — an injected seam replaces it, absent = today's tools.
|
|
1417
|
+
const clock = tools.now ?? Date.now;
|
|
1418
|
+
const kubectl = tools.kubectl ?? defaultKubectl;
|
|
1419
|
+
const ssh = tools.ssh ?? defaultSshCommand;
|
|
1420
|
+
const remoteRun = tools.remoteRun ?? defaultRemoteRun;
|
|
1421
|
+
const runPod = tools.podCommand ?? podCommand;
|
|
1266
1422
|
// GATE-HOSTMIRROR-EXPLICIT: resolved once per runner and handed to every podCommand (shard · retry · recheck).
|
|
1267
1423
|
let gateMirror: GateHostMirror | undefined;
|
|
1268
1424
|
const gateHostMirror = (pod: NonNullable<GateOptions['pod']>): { hostMirror?: string } => {
|
|
@@ -1283,15 +1439,21 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1283
1439
|
// GNU timeout signals its own process group: -s KILL would kill timeout too (ssh 255), so TERM first then KILL after 10s (124 / 137).
|
|
1284
1440
|
// No coreutils timeout on the remote → run unlimited there and rely on the local ssh deadline.
|
|
1285
1441
|
const limitPrefix = limitMs ? `$(command -v timeout >/dev/null 2>&1 && echo 'timeout -k 10 ${Math.ceil(limitMs / 1000)}') ` : '';
|
|
1286
|
-
|
|
1287
|
-
// the remote command still `cd`s to the quoted path, so either spelling is the same directory.
|
|
1288
|
-
const run = spawnSync('ssh', [remote, `PATH=$HOME/.bun/bin:/opt/homebrew/bin:$PATH; export PATH; cd ${quote(cwd)} && ${limitPrefix}${[cmd, ...args].map(quote).join(' ')}`],
|
|
1289
|
-
{ cwd: '/tmp', encoding: 'utf8', maxBuffer: 128 * 1024 * 1024, ...(limitMs ? { timeout: limitMs + 60_000, killSignal: 'SIGKILL' as const } : {}) });
|
|
1290
|
-
const timedOut = !!limitMs && (run.status === 124 || run.status === 137 || (run.error as NodeJS.ErrnoException | undefined)?.code === 'ETIMEDOUT');
|
|
1291
|
-
return { rc: run.status ?? 2, output: `${run.stdout ?? ''}\n${run.stderr ?? ''}${run.error ? `\n${run.error}` : ''}`, ...(timedOut ? { timedOut } : {}) };
|
|
1442
|
+
return ssh(remote, `PATH=$HOME/.bun/bin:/opt/homebrew/bin:$PATH; export PATH; cd ${quote(cwd)} && ${limitPrefix}${[cmd, ...args].map(quote).join(' ')}`, limitMs);
|
|
1292
1443
|
}
|
|
1293
1444
|
: localCommand);
|
|
1445
|
+
const rulesGateBaseFor = async (commit: string, files: readonly string[]): Promise<string | undefined> => {
|
|
1446
|
+
if (!files.includes('test/rules-contract.test.ts')) return undefined;
|
|
1447
|
+
// The gate mirror only receives pinned commit refs, so its Pod checkout has no origin/main.
|
|
1448
|
+
// Resolve the same merge-base in the source repository and pass the exact commit to the Pod.
|
|
1449
|
+
const resolved = await localCommand('git', ['merge-base', commit, 'origin/main'], repo);
|
|
1450
|
+
if (resolved.rc !== 0) throw new Error(`rules gate base unavailable for ${commit.slice(0, 12)}: ${tail40(resolved.output)}`);
|
|
1451
|
+
const base = resolved.output.trim();
|
|
1452
|
+
if (!/^[0-9a-f]{40}$/.test(base)) throw new Error(`rules gate base invalid for ${commit.slice(0, 12)}: ${base}`);
|
|
1453
|
+
return base;
|
|
1454
|
+
};
|
|
1294
1455
|
const podSweep = async (tree: string, logDir: string | undefined, pod: NonNullable<GateOptions['pod']>, only?: readonly string[]): Promise<CommandResult> => {
|
|
1456
|
+
const sweepId = randomUUID();
|
|
1295
1457
|
const shardCount = pod.shards ?? 24;
|
|
1296
1458
|
const isolationConcurrency = pod.isolationConcurrency ?? 4;
|
|
1297
1459
|
// GATE-TIMEOUT-HEAL ①: the configured value is the floor; each shard's own deadline is shardDeadlineSeconds(planned).
|
|
@@ -1303,7 +1465,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1303
1465
|
check(head, 'cut tree HEAD');
|
|
1304
1466
|
const commit = head.output.trim();
|
|
1305
1467
|
if (!/^[0-9a-f]{40}$/.test(commit)) throw new Error('invalid cut tree HEAD');
|
|
1306
|
-
const selectionStart =
|
|
1468
|
+
const selectionStart = clock();
|
|
1307
1469
|
const listed = await command('git', ['ls-files', '*.test.*'], tree);
|
|
1308
1470
|
check(listed, 'git ls-files tests');
|
|
1309
1471
|
const allFiles = listed.output.split(/\r?\n/).filter((file) => /\.test\.(?:tsx?|jsx?|mts|cts)$/.test(file)).sort();
|
|
@@ -1313,6 +1475,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1313
1475
|
const missing = only?.filter((file) => !allFiles.includes(file)) ?? [];
|
|
1314
1476
|
if (missing.length) throw new Error(`partial plan file missing at this commit: ${missing.slice(0, 5).join(', ')}`);
|
|
1315
1477
|
const files = onlySet ? allFiles.filter((file) => onlySet.has(file)) : allFiles;
|
|
1478
|
+
const rulesBaseSha = await rulesGateBaseFor(commit, files);
|
|
1316
1479
|
if (files.some((file) => file.startsWith('-') || file.startsWith('/') || file.startsWith('./')
|
|
1317
1480
|
|| file.split('/').some((part) => !part || part === '.' || part === '..') || /[\r\n]/.test(file))) throw new Error('unsafe pod test path');
|
|
1318
1481
|
if (new Set(files).size !== files.length) throw new Error('duplicate pod test path');
|
|
@@ -1332,12 +1495,14 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1332
1495
|
// The TD1 measurements predate the fixture-only gate; do not isolate those five files at their old 2–13 GB peaks.
|
|
1333
1496
|
const fixtureOnly = new Set([...GATE_NIGHTLY_AUDITS, 'test/guardian/dispatch-surface-contract.test.ts', 'test/user-config-mcp.test.ts']);
|
|
1334
1497
|
const heavy = new Set(assignable.filter((file) => !fixtureOnly.has(file) && (memory.get(file) ?? 0) >= POD_HEAVY_FILE_MB));
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
if (
|
|
1498
|
+
// GATE-RISK-LANE — the failure history this sweep already keeps (`gateHistory().watchList`) puts its watched files in
|
|
1499
|
+
// the first shard; heavy files stay solo and spawn files keep their own lane, so the rest of the plan is unchanged.
|
|
1500
|
+
const watchList = pod.riskWatch ? readGateRiskWatchList(pod.riskWatch) : [];
|
|
1501
|
+
const riskPlan = planGateRiskLaneShards({ assignable, durations, shardCount, heavy, watchList });
|
|
1502
|
+
const shards = riskPlan.shards;
|
|
1503
|
+
if (riskPlan.spawnFiles.length) debug.log('release-loop.gate', 'pod-spawn-lane', { files: riskPlan.spawnFiles, shards: riskPlan.spawnShardCount });
|
|
1504
|
+
if (riskPlan.riskFiles.length) debug.log('release-loop.gate', 'pod-risk-lane-plan', { files: riskPlan.riskFiles, shard: 0,
|
|
1505
|
+
plannedSeconds: riskPlan.riskPlannedSeconds, shards: shards.length, watch: watchList.length });
|
|
1341
1506
|
logPlanWalltime(planWall, shards);
|
|
1342
1507
|
if (heavy.size) debug.log('release-loop.gate', 'pod-heavy-alone', { files: [...heavy], thresholdMb: POD_HEAVY_FILE_MB, memoryLimit: POD_ISOLATION_MEMORY_LIMIT });
|
|
1343
1508
|
const known = assignable.filter((file) => durations.has(file)).length;
|
|
@@ -1346,14 +1511,14 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1346
1511
|
maxPlannedSeconds: Math.max(...shards.map((shard) => shard.plannedSeconds)),
|
|
1347
1512
|
source: durations.size ? pod.durationSource : 'none',
|
|
1348
1513
|
});
|
|
1349
|
-
reportSelectionTimed(
|
|
1514
|
+
reportSelectionTimed(clock() - selectionStart, assignable.length, pod.onSelectionTimed);
|
|
1350
1515
|
// GATE-SPEED A3①: shard Pod CPU (`release.loop.gatePodCpu` · default request 1 / limit 4) — shards × request must fit the pool.
|
|
1351
1516
|
const cpu = resolvePodCpu(pod.cpu);
|
|
1352
1517
|
// Only evidence-backed opt-in files reach this batch; every other file retains its own Pod below.
|
|
1353
1518
|
const batchWidth = Math.max(1, Math.min(4, Math.floor(podCpuMillis(cpu.limit)! / 1000)));
|
|
1354
1519
|
debug.log('release-loop.gate', 'shard-resources', { shards: shards.length, cpuRequest: cpu.request, cpuLimit: cpu.limit, cpuSource: pod.cpu ? 'config' : 'default' });
|
|
1355
1520
|
debug.log('release-loop.gate', 'isolation-batch-width', { batchWidth, cpuLimit: cpu.limit });
|
|
1356
|
-
const ready = poolOverride ? undefined : checkPodPool(parsePodPool(pod.pool),
|
|
1521
|
+
const ready = poolOverride ? undefined : checkPodPool(parsePodPool(pod.pool), kubectl);
|
|
1357
1522
|
if (ready && !ready.ok) throw new Error('pod command: 풀의 노드가 하나도 준비되지 않았다');
|
|
1358
1523
|
const poolScheduler = poolOverride ?? new PodPoolScheduler(ready!.ready);
|
|
1359
1524
|
const configCache = typeof pod.bunCache === 'string' ? pod.bunCache.trim() : '';
|
|
@@ -1369,7 +1534,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1369
1534
|
const admissionWaitSeconds = pod.admissionWaitSeconds ?? GATE_ADMISSION_WAIT_SECONDS_DEFAULT;
|
|
1370
1535
|
if (!Number.isSafeInteger(admissionWaitSeconds) || admissionWaitSeconds < 0) throw new Error('invalid pod sweep options');
|
|
1371
1536
|
// An injected pool (tests) runs without admission unless `pod.admission` is passed; the gate's own pool asks it.
|
|
1372
|
-
const gateAdmission = pod.admission ?? (poolOverride ? undefined : poolGateAdmission(poolScheduler));
|
|
1537
|
+
const gateAdmission = pod.admission ?? tools.admission ?? (poolOverride ? undefined : poolGateAdmission(poolScheduler));
|
|
1373
1538
|
debug.log('release-loop.gate', 'pod-admission', { waitSeconds: admissionWaitSeconds, source: pod.admissionWaitSeconds !== undefined ? 'config' : 'default', enabled: !!gateAdmission && admissionWaitSeconds > 0 });
|
|
1374
1539
|
// GATE-POD-STALL-FAILFAST: a shard with no test output within the stall window is stopped and recorded unmeasured.
|
|
1375
1540
|
const stallSeconds = pod.stallSeconds ?? GATE_POD_STALL_SECONDS_DEFAULT;
|
|
@@ -1378,14 +1543,14 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1378
1543
|
// An injected pool (tests) runs without the watch unless `pod.liveLog` is passed — never a real kubectl from a test.
|
|
1379
1544
|
const liveLog = pod.liveLog ?? (poolOverride ? undefined : (job: { name: string; context?: string }) => {
|
|
1380
1545
|
if (!job.context) return null;
|
|
1381
|
-
const logs =
|
|
1546
|
+
const logs = kubectl(['--context', job.context, '-n', 'elanous-test', 'logs', `job/${job.name}`, '-c', 'child', '--tail=400']);
|
|
1382
1547
|
return logs.status === 0 ? logs.stdout : null;
|
|
1383
1548
|
});
|
|
1384
1549
|
const stopJob = pod.stopJob ?? ((job: { name: string; context?: string }) => {
|
|
1385
1550
|
// Not `delete job`: runPodCommand would poll a missing Job forever. A 1 s deadline makes the Job fail now and
|
|
1386
1551
|
// runPodCommand returns through its normal path (logs · exit code · Secret cleanup).
|
|
1387
1552
|
if (!job.context) return false;
|
|
1388
|
-
const patched =
|
|
1553
|
+
const patched = kubectl(['--context', job.context, '-n', 'elanous-test', 'patch', 'job', job.name, '--type=merge', '-p', JSON.stringify({ spec: { activeDeadlineSeconds: 1 } })]);
|
|
1389
1554
|
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' });
|
|
1390
1555
|
return patched.status === 0;
|
|
1391
1556
|
});
|
|
@@ -1396,7 +1561,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1396
1561
|
if (!Number.isFinite(midRunStallSeconds) || midRunStallSeconds < 0) throw new Error('invalid pod sweep options');
|
|
1397
1562
|
const abortTests = pod.abortTests ?? (poolOverride ? undefined : (job: { name: string; context?: string }, reason: string) => {
|
|
1398
1563
|
if (!job.context) return false;
|
|
1399
|
-
const ran =
|
|
1564
|
+
const ran = kubectl(['--context', job.context, '-n', 'elanous-test', 'exec', `job/${job.name}`, '-c', 'child', '--', 'bash', '-c', gateShardAbortScript(reason)]);
|
|
1400
1565
|
if (ran.status !== 0) debug.log('release-loop.gate', 'shard-abort-failed', { job: job.name, rc: ran.status, out: `${ran.stdout}${ran.stderr}`.trim().slice(0, 300) }, { level: 'warn' });
|
|
1401
1566
|
return ran.status === 0;
|
|
1402
1567
|
});
|
|
@@ -1408,7 +1573,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1408
1573
|
const downMembers = new Set<string>();
|
|
1409
1574
|
let tailPlacedHost: string | undefined;
|
|
1410
1575
|
const multiHost = poolScheduler.members.length > 1;
|
|
1411
|
-
const probeMember = pod.probeMember ?? (poolOverride ? undefined : defaultGateMemberProbe);
|
|
1576
|
+
const probeMember = pod.probeMember ?? (poolOverride ? undefined : (member: PodPoolMember) => defaultGateMemberProbe(member, { kubectl, remoteRun }));
|
|
1412
1577
|
const memberUnreadableSeconds = pod.memberUnreadableSeconds ?? GATE_MEMBER_UNREADABLE_SECONDS_DEFAULT;
|
|
1413
1578
|
if (!Number.isFinite(memberUnreadableSeconds) || memberUnreadableSeconds < 0) throw new Error('invalid pod sweep options');
|
|
1414
1579
|
debug.log('release-loop.gate', 'pod-roster', { members: poolScheduler.members.map((m) => ({ context: m.context, host: m.sshHost ?? null, capacity: m.capacity })),
|
|
@@ -1429,11 +1594,38 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1429
1594
|
const jobLogTail = (job: PodCommandResult): string => {
|
|
1430
1595
|
if (podLogTail) return podLogTail(job);
|
|
1431
1596
|
for (const member of poolScheduler.members.filter((m) => !downMembers.has(m.context))) {
|
|
1432
|
-
const logs =
|
|
1597
|
+
const logs = kubectl(['--context', member.context, '-n', 'elanous-test', 'logs', `job/${job.job}`, '-c', 'child', '--tail=200']);
|
|
1433
1598
|
if (logs.status === 0 && logs.stdout) return logs.stdout;
|
|
1434
1599
|
}
|
|
1435
1600
|
return '';
|
|
1436
1601
|
};
|
|
1602
|
+
const failureEvidence = pod.failureEvidence ?? ((job: { name: string; context?: string }) => {
|
|
1603
|
+
if (!job.context) return { error: '수거 실패 · Job 배치 context 없음' };
|
|
1604
|
+
const prefix = ['--context', job.context, '--request-timeout=5s', '-n', 'elanous-test'];
|
|
1605
|
+
const errors: string[] = [];
|
|
1606
|
+
let termination: { reason?: string; message?: string; exitCode?: number; finishedAt?: string } | undefined;
|
|
1607
|
+
let podName: string | undefined;
|
|
1608
|
+
let latestEndedAt = -Infinity;
|
|
1609
|
+
const status = (pod.failureKubectl ?? kubectl)([...prefix, 'get', 'pods', '-l', `job-name=${job.name}`, '-o', 'json']);
|
|
1610
|
+
if (status.status !== 0) errors.push(`Pod 상태: ${status.stderr.trim() || `kubectl rc=${status.status}`}`);
|
|
1611
|
+
else try {
|
|
1612
|
+
const pods = JSON.parse(status.stdout) as { items?: Array<{ metadata?: { name?: string; creationTimestamp?: string }; status?: { containerStatuses?: Array<{ name?: string; state?: { terminated?: typeof termination } }> } }> };
|
|
1613
|
+
for (const item of pods.items ?? []) {
|
|
1614
|
+
const child = item.status?.containerStatuses?.find((container) => container.name === 'child');
|
|
1615
|
+
const ended = child?.state?.terminated;
|
|
1616
|
+
if (!ended || !item.metadata?.name) continue;
|
|
1617
|
+
const endedAt = Date.parse(ended.finishedAt ?? item.metadata.creationTimestamp ?? '');
|
|
1618
|
+
const rank = Number.isFinite(endedAt) ? endedAt : -Infinity;
|
|
1619
|
+
if (!termination || rank > latestEndedAt) { podName = item.metadata.name; termination = ended; latestEndedAt = rank; }
|
|
1620
|
+
}
|
|
1621
|
+
if (!termination) errors.push('Pod 상태: child 종료 사유 없음');
|
|
1622
|
+
} catch (error) { errors.push(`Pod 상태 JSON: ${String(error)}`); }
|
|
1623
|
+
const logs = podName ? (pod.failureKubectl ?? kubectl)([...prefix, 'logs', `pod/${podName}`, '-c', 'child', '--tail=100']) : undefined;
|
|
1624
|
+
if (!logs) errors.push('로그: 종료된 Pod 이름 없음');
|
|
1625
|
+
else if (logs.status !== 0) errors.push(`로그: ${logs.stderr.trim() || `kubectl rc=${logs.status}`}`);
|
|
1626
|
+
return { ...(podName ? { podName } : {}), ...(termination ? { termination } : {}), ...(logs?.status === 0 ? { logTail: logs.stdout } : {}),
|
|
1627
|
+
...(errors.length ? { error: `수거 실패 · ${errors.join(' · ')}` } : {}) };
|
|
1628
|
+
});
|
|
1437
1629
|
type ShardRun = { output: string; rc: number; pass: number; fail: number; errors: number; ran: number; files: number; passedIds: string[] };
|
|
1438
1630
|
type ShardReason = NonNullable<GateResult['stalledShards']>[number]['reason'];
|
|
1439
1631
|
// 깊이 끝 OOM 조각은 «조각 전체»를 파일 하나씩 격리한다 — 상한 40 이면 나머지가 못 잰 채 남았다(09-30 ① 7번 조각 110 파일).
|
|
@@ -1502,17 +1694,18 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1502
1694
|
const shardDeadline = shardDeadlineSeconds(batchIsolation ? Math.max(...paths.map((file) => estimated([file]))) * Math.ceil(paths.length / batchWidth) : estimated(paths), deadlineSeconds);
|
|
1503
1695
|
const parts = batchIsolation ? paths.map((file) => [file]) : shardParts(paths, (file) => estimated([file]));
|
|
1504
1696
|
const memoryLimit = batchIsolation || paths.length === 1 && (depth >= 1 || heavy.has(paths[0]!)) ? POD_ISOLATION_MEMORY_LIMIT : undefined;
|
|
1505
|
-
const shellOptions = { parts, cdpPatterns, softSeconds: softDeadlineSeconds(shardDeadline), cachePrefix, installSlots, ...(batchIsolation ? { parallelParts: batchWidth } : {}) };
|
|
1697
|
+
const shellOptions = { parts, cdpPatterns, softSeconds: softDeadlineSeconds(shardDeadline), cachePrefix, installSlots, ...(rulesBaseSha ? { rulesBaseSha } : {}), ...(batchIsolation ? { parallelParts: batchWidth } : {}) };
|
|
1506
1698
|
const keyShell = gateShardShell(shellOptions);
|
|
1507
1699
|
// The stall beat only reports; runKey keeps hashing the shell without it, so earlier results stay reusable.
|
|
1508
1700
|
const runKey = createHash('sha256').update(JSON.stringify({ pool: pod.pool, podShell: keyShell, memoryLimit: memoryLimit ?? null, deadlineSeconds: shardDeadline, bunCache: bunCache ?? null })).digest('hex');
|
|
1509
|
-
let start =
|
|
1701
|
+
let start = clock();
|
|
1510
1702
|
let admitted: GateAdmissionResult | undefined;
|
|
1511
1703
|
let output: string | undefined;
|
|
1512
1704
|
const phaseTimes: Record<string, string> = {};
|
|
1513
1705
|
const stamp = (phase: string) => { phaseTimes[phase] = new Date().toISOString(); };
|
|
1514
1706
|
let podStages: Record<string, number> | undefined;
|
|
1515
1707
|
let podLifecycle: Record<string, string> | undefined;
|
|
1708
|
+
let imageDigest: string | null = null;
|
|
1516
1709
|
let podJobName: string | undefined;
|
|
1517
1710
|
let batchArtifactsComplete = true;
|
|
1518
1711
|
let junit: string | undefined;
|
|
@@ -1522,6 +1715,13 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1522
1715
|
let lastFile: string | undefined;
|
|
1523
1716
|
let jobFailed = false;
|
|
1524
1717
|
let jobError = '';
|
|
1718
|
+
let failureCapture: ReturnType<NonNullable<NonNullable<GateOptions['pod']>['failureEvidence']>> | undefined;
|
|
1719
|
+
let jobNameForCapture: string | undefined;
|
|
1720
|
+
let jobContextForCapture: string | undefined;
|
|
1721
|
+
const captureFailure = () => {
|
|
1722
|
+
try { failureCapture = failureEvidence({ name: jobNameForCapture ?? 'unplaced', context: jobContextForCapture }); }
|
|
1723
|
+
catch (error) { failureCapture = { error: `수거 실패 · ${String(error)}` }; }
|
|
1724
|
+
};
|
|
1525
1725
|
/** GATE-MULTI-HOST — the member this shard's Job was placed on (recorded in the shard json / log / board). */
|
|
1526
1726
|
let placedOn: string | undefined;
|
|
1527
1727
|
let timedOut = false;
|
|
@@ -1570,8 +1770,9 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1570
1770
|
if (admitted.outcome !== 'off') debug.log('release-loop.gate', 'shard-admission', { shard, branch, outcome: admitted.outcome, waitedMs: admitted.waitedMs, ...(admitted.reason ? { reason: admitted.reason } : {}) },
|
|
1571
1771
|
admitted.outcome === 'granted' ? undefined : { level: 'warn' });
|
|
1572
1772
|
// The admission wait is not the shard's run time: deadline/timeout judgment and the measured duration start here.
|
|
1573
|
-
start =
|
|
1773
|
+
start = clock();
|
|
1574
1774
|
const jobName = `gate-${randomUUID()}`;
|
|
1775
|
+
jobNameForCapture = jobName;
|
|
1575
1776
|
// The member's context is known once the Job is placed; the live log is read (and the Job stopped) there.
|
|
1576
1777
|
let jobContext: string | undefined;
|
|
1577
1778
|
const liveJob = () => ({ name: jobName, ...(jobContext ? { context: jobContext } : {}) });
|
|
@@ -1592,9 +1793,16 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1592
1793
|
},
|
|
1593
1794
|
} : {}) }) : undefined;
|
|
1594
1795
|
const placed = admitted.release;
|
|
1595
|
-
const job = await
|
|
1796
|
+
const job = await runPod({
|
|
1797
|
+
provenanceGate: true,
|
|
1798
|
+
provenance: { actorKind: process.env.ELANOUS_SEAT ? 'seat' : 'loop', actor: process.env.ELANOUS_SEAT || 'release-loop',
|
|
1799
|
+
character: 'gate-shard-runner', launchHost: hostname(), entrance: process.env.ELANOUS_RELEASE_ENTRANCE?.trim() || 'release-run',
|
|
1800
|
+
kind: 'gate-shard', run: process.env.ELANOUS_RUN_ID || sweepId, phase: 'release-gate', requestedAt: new Date().toISOString(),
|
|
1801
|
+
graphNode: 'gate', version: pod.shardsFile?.version, commit, shard: `${shard + 1}-of-${shards.length}`,
|
|
1802
|
+
reason: `release gate shard ${shard + 1}/${shards.length}`, pool: pod.pool, cpuReq: cpu.request,
|
|
1803
|
+
memReq: memoryLimit ?? POD_ISOLATION_MEMORY_LIMIT, warmReused: false },
|
|
1596
1804
|
pool: pod.pool, poolScheduler: trackedPool(shard, depth > 0 || branch !== '', (member) => {
|
|
1597
|
-
stamp('poolPlaced'); jobContext = member.context; placedOn = member.context; placed();
|
|
1805
|
+
stamp('poolPlaced'); jobContext = member.context; jobContextForCapture = member.context; placedOn = member.context; placed();
|
|
1598
1806
|
if (branch === '-tail') tailPlacedHost = member.context;
|
|
1599
1807
|
}, skipMembers), clone: true, source: { kind: 'commit', sha: commit }, deadlineSeconds: shardDeadline,
|
|
1600
1808
|
...gateHostMirror(pod),
|
|
@@ -1609,13 +1817,20 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1609
1817
|
}).finally(() => watch?.stop());
|
|
1610
1818
|
stamp('jobReturned');
|
|
1611
1819
|
podJobName = job.job;
|
|
1820
|
+
// poolOverride is the injected scheduler used by isolated tests; it has no authenticated kubectl
|
|
1821
|
+
// context. Those sweeps intentionally cannot mint a valid baseline receipt (missing observed digest).
|
|
1822
|
+
// Production pool sweeps always use this Kubernetes lookup; lookup failure is receipt-invalid only.
|
|
1612
1823
|
if (!poolOverride && placedOn) {
|
|
1613
1824
|
try {
|
|
1614
|
-
const got =
|
|
1615
|
-
if (got.status === 0)
|
|
1825
|
+
const got = kubectl(['--context', placedOn, '-n', 'elanous-test', 'get', 'pods', '-l', `job-name=${job.job}`, '-o', 'json']);
|
|
1826
|
+
if (got.status === 0) {
|
|
1827
|
+
podLifecycle = readGatePodLifecycle(got.stdout);
|
|
1828
|
+
imageDigest = actualGatePodImageDigest(got.stdout);
|
|
1829
|
+
}
|
|
1616
1830
|
} catch { /* timings never change the verdict */ }
|
|
1617
1831
|
}
|
|
1618
1832
|
jobExitCode = job.exitCode;
|
|
1833
|
+
if (job.exitCode !== 0) captureFailure();
|
|
1619
1834
|
batchArtifactDir = job.artifactsDir;
|
|
1620
1835
|
const read = readShardArtifacts(job.artifactsDir, parts);
|
|
1621
1836
|
batchArtifactsComplete = !batchIsolation || parts.every((_, index) =>
|
|
@@ -1640,7 +1855,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1640
1855
|
if (abort?.taken) abortedShards++;
|
|
1641
1856
|
if (stalledAt && !finished.length && rc === undefined) stall = { seconds: stallSeconds, sinceCheckoutMs: stalledAt.sinceCheckoutMs };
|
|
1642
1857
|
// A Job the cluster cut at its own deadline leaves no outbox at all: past the deadline with nothing is a timeout too.
|
|
1643
|
-
if (!timedOut && output === undefined && job.exitCode !== 0 &&
|
|
1858
|
+
if (!timedOut && output === undefined && job.exitCode !== 0 && clock() - start >= shardDeadline * 1000) timedOut = true;
|
|
1644
1859
|
lastFile = output === undefined && unfinishedLog === undefined ? undefined : lastStartedTestFile(`${output ?? ''}\n${unfinishedLog ?? ''}`, paths);
|
|
1645
1860
|
if (!lastFile && (job.exitCode !== 0 || rc === undefined || (rc !== 0 && rc !== 1))) {
|
|
1646
1861
|
try { lastFile = lastStartedTestFile(jobLogTail(job).split(/\r?\n/).slice(-200).join('\n'), paths); }
|
|
@@ -1649,10 +1864,11 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1649
1864
|
} catch (error) {
|
|
1650
1865
|
jobFailed = true;
|
|
1651
1866
|
jobError = error instanceof Error ? error.message : String(error);
|
|
1867
|
+
if (!failureCapture) captureFailure();
|
|
1652
1868
|
}
|
|
1653
1869
|
// A launch that failed before placement still gives its admission back (release is idempotent).
|
|
1654
1870
|
admitted?.release();
|
|
1655
|
-
const durationMs =
|
|
1871
|
+
const durationMs = clock() - start;
|
|
1656
1872
|
const fileSeconds = junit === undefined ? undefined : Object.fromEntries(readJunitFileSeconds(junit));
|
|
1657
1873
|
const testSeconds = fileSeconds === undefined ? undefined : Object.values(fileSeconds).reduce((sum, seconds) => sum + seconds, 0);
|
|
1658
1874
|
const testWallSeconds = batchIsolation
|
|
@@ -1683,13 +1899,25 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1683
1899
|
if (unfinishedLog !== undefined) writeFileSync(unfinishedDestination, unfinishedLog, { mode: 0o600 });
|
|
1684
1900
|
else rmSync(unfinishedDestination, { force: true });
|
|
1685
1901
|
writeFileSync(join(logDir, `pod-${shard}${branch}.json`), JSON.stringify({ durationMs, rc: rc ?? null,
|
|
1686
|
-
...(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, ...(placedOn ? { host: placedOn } : {}),
|
|
1902
|
+
...(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, sweepId, commit, runKey, attemptId, ...(parentAttempt ? { parentAttempt } : {}), jobExitCode: jobExitCode ?? null, jobFailed, ...(placedOn ? { host: placedOn } : {}),
|
|
1687
1903
|
...(Object.keys(phaseTimes).length ? { phaseTimes } : {}), ...(podStages ? { podStages } : {}),
|
|
1688
1904
|
...(podLifecycle && Object.keys(podLifecycle).length ? { podLifecycle } : {}),
|
|
1905
|
+
...(failureCapture ? { failureCapture: {
|
|
1906
|
+
...(failureCapture.podName ? { podName: failureCapture.podName } : {}),
|
|
1907
|
+
...(failureCapture.termination ? { termination: {
|
|
1908
|
+
...failureCapture.termination,
|
|
1909
|
+
...(failureCapture.termination.reason ? { reason: redactSecretText(failureCapture.termination.reason) } : {}),
|
|
1910
|
+
...(failureCapture.termination.message ? { message: redactSecretText(failureCapture.termination.message) } : {}),
|
|
1911
|
+
} } : {}),
|
|
1912
|
+
...(failureCapture.logTail !== undefined ? { logTail: redactedGateLogTail(failureCapture.logTail) } : {}),
|
|
1913
|
+
...(failureCapture.error ? { error: redactSecretText(failureCapture.error) } : {}),
|
|
1914
|
+
} } : {}),
|
|
1915
|
+
...(imageDigest ? { imageDigest } : {}),
|
|
1689
1916
|
...(fileSeconds !== undefined ? { fileSeconds, testSeconds } : {}), ...(testWallSeconds !== undefined ? { testWallSeconds } : {}),
|
|
1690
1917
|
...(podOverheadSeconds !== undefined ? { podOverheadSeconds } : {}),
|
|
1691
1918
|
...(unfinishedLogBytes !== undefined ? { unfinishedLogBytes } : {}),
|
|
1692
|
-
...(admitted && admitted.outcome !== 'off' ? { admission: admitted.outcome, admissionWaitMs: admitted.waitedMs
|
|
1919
|
+
...(admitted && admitted.outcome !== 'off' ? { admission: admitted.outcome, admissionWaitMs: admitted.waitedMs,
|
|
1920
|
+
...(admitted.reason ? { admissionReason: admitted.reason } : {}) } : {}),
|
|
1693
1921
|
...(abort ? { abort: { reason: `abort: ${abort.reason}`, at: abort.at, by: abort.by, sinceLastOutputMs: abort.sinceLastOutputMs, taken: abort.taken } } : {}),
|
|
1694
1922
|
stallWatch: shardStallWatchRecord(stallSeconds, !!liveLog, stallWatchState) }) + '\n', { mode: 0o600 });
|
|
1695
1923
|
if (paths.length === 1 && (rc === 0 || rc === 1) && !jobFailed && jobExitCode === 0
|
|
@@ -1904,9 +2132,13 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1904
2132
|
ran: Number(ran![1]), files: Number(ran![2]), passedIds: junit ? junitPassedIds(junit) : [] }], stalled: [] };
|
|
1905
2133
|
};
|
|
1906
2134
|
// 모든 조각이 끝난 뒤 판정한다 — 한 조각의 예외로 먼저 돌아가면 다른 Pod 가 도는 채로 정리가 시작된다(#22002 리뷰 R3).
|
|
2135
|
+
// GATE-RISK-LANE — the risk shard's own wall time (planned vs measured in its log line); it never changes a verdict.
|
|
2136
|
+
const shardDurations: number[] = [];
|
|
1907
2137
|
const settled = await Promise.allSettled(shards.map(async (item, shard) => {
|
|
2138
|
+
const shardStartedAt = clock();
|
|
1908
2139
|
try {
|
|
1909
2140
|
const done = await runShard(item.files, shard);
|
|
2141
|
+
shardDurations[shard] = clock() - shardStartedAt;
|
|
1910
2142
|
const state = !done.stalled.length ? 'done' as const : done.stalled.some((stall) => stall.reason === 'timeout') ? 'timeout' as const : 'failed' as const;
|
|
1911
2143
|
markShard(shard, { state, endedAt: new Date().toISOString() }, ['waitReason']);
|
|
1912
2144
|
return done;
|
|
@@ -1918,6 +2150,16 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1918
2150
|
const rejected = settled.find((result): result is PromiseRejectedResult => result.status === 'rejected');
|
|
1919
2151
|
if (rejected) throw rejected.reason;
|
|
1920
2152
|
const results = settled.flatMap((result) => result.status === 'fulfilled' ? [result.value] : []);
|
|
2153
|
+
// GATE-RISK-LANE — planned vs measured for the shard that ran first, with its own result. Its Pods are ordinary
|
|
2154
|
+
// shard Pods: their runs join `measured` below, so the sweep's summary and every later judgment are unchanged.
|
|
2155
|
+
let riskLane: GateRiskLaneRecord | undefined;
|
|
2156
|
+
if (riskPlan.riskFiles.length) {
|
|
2157
|
+
const riskRuns = results[0]?.runs ?? [];
|
|
2158
|
+
riskLane = { files: [...riskPlan.riskFiles], plannedSeconds: riskPlan.riskPlannedSeconds,
|
|
2159
|
+
durationMs: shardDurations[0] ?? 0, rc: riskRuns.length ? (riskRuns.some((run) => run.fail + run.errors > 0) ? 1 : 0) : null };
|
|
2160
|
+
debug.log('release-loop.gate', 'pod-risk-lane', { files: riskLane.files, shard: 0, plannedSeconds: riskLane.plannedSeconds,
|
|
2161
|
+
durationMs: riskLane.durationMs, rc: riskLane.rc, ...(results[0]?.stalled[0] ? { reason: results[0].stalled[0]!.reason } : {}) });
|
|
2162
|
+
}
|
|
1921
2163
|
let stalledShards = results.flatMap((result) => result.stalled);
|
|
1922
2164
|
const measured = results.flatMap((result) => result.runs);
|
|
1923
2165
|
// GATE-STALLED-TAIL-REMEASURE: after the sweep, give only a one-file no-output leaf one fresh attempt on a different
|
|
@@ -1963,13 +2205,19 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1963
2205
|
stallSeconds, measuredFiles: total.files }, { level: 'warn' });
|
|
1964
2206
|
// An unmeasured (stalled) shard is never handed to the host re-run as an environment stall: the gate fails now.
|
|
1965
2207
|
if (abortedShards) debug.log('release-loop.gate', 'pod-sweep-aborted', { shards: abortedShards, midRunStallSeconds }, { level: 'warn' });
|
|
1966
|
-
if (stalledShards.some((item) => item.reason !== 'no-output' || item.files.length !== 1 || item.stall))
|
|
2208
|
+
if (stalledShards.some((item) => item.reason !== 'no-output' || item.files.length !== 1 || item.stall)) {
|
|
2209
|
+
const measuredFailures = partialFailuresOf(measured);
|
|
2210
|
+
throw new StalledPodShards(stalledShards, total, measuredFailures,
|
|
2211
|
+
results.filter((item) => item.stalled.length === 0).length, abortedShards);
|
|
2212
|
+
}
|
|
1967
2213
|
if (total.ran === 0 && total.errors === 0 && !stalledShards.length) throw new Error('sweep incomplete: no tests ran');
|
|
1968
2214
|
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, ''));
|
|
1969
2215
|
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`, validatedShards: true,
|
|
2216
|
+
podSweep: { id: sweepId, shards: shards.length, files: assignable },
|
|
1970
2217
|
passedIds: [...new Set(measured.flatMap((run) => run.passedIds))].sort(),
|
|
1971
2218
|
stalledEnv: stalledShards.flatMap((item) => item.files.map((file) => ({ file, reason: 'no-output' as const }))),
|
|
1972
|
-
...(abortedShards ? { abortedShards } : {})
|
|
2219
|
+
...(abortedShards ? { abortedShards } : {}),
|
|
2220
|
+
...(riskLane ? { riskLane } : {}) };
|
|
1973
2221
|
};
|
|
1974
2222
|
const runPodRecheckBatch = async (files: readonly string[], commit: string, pod: NonNullable<GateOptions['pod']>, timeoutMs: number, relaxed = false): Promise<Map<string, CommandResult>> => {
|
|
1975
2223
|
const warmShadowScope = pod.warmShadowScope ?? { requested: false };
|
|
@@ -1992,6 +2240,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1992
2240
|
|| file.split('/').some((part) => part === '.' || part === '..')))
|
|
1993
2241
|
throw new Error('unsafe pod recheck files');
|
|
1994
2242
|
if (!sha.test(commit)) throw new Error('invalid pod recheck commit');
|
|
2243
|
+
const rulesBaseSha = await rulesGateBaseFor(commit, files);
|
|
1995
2244
|
const installSlots = pod.installSlots ?? POD_INSTALL_SLOTS_DEFAULT;
|
|
1996
2245
|
if (!Number.isSafeInteger(installSlots) || installSlots < 0) throw new Error('invalid pod recheck install slots');
|
|
1997
2246
|
const bunCache = pod.bunCache?.trim() || process.env[POD_BUN_CACHE_HOST_PATH]?.trim();
|
|
@@ -2000,18 +2249,51 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2000
2249
|
const deadlineSeconds = partLimitSeconds * Math.ceil(files.length / 2) + POD_SHARD_EMIT_RESERVE_SECONDS + 60;
|
|
2001
2250
|
const shell = gateShardShell({ parts: files.map((file) => [file]), cdpPatterns: [], softSeconds: softDeadlineSeconds(deadlineSeconds),
|
|
2002
2251
|
cachePrefix: bunCache ? podBunCacheVolume(bunCache).shellPrefix : '', installSlots, parallelParts: files.length > 1 ? 2 : 1,
|
|
2003
|
-
partLimitSeconds, sourceBinding: { cutSha: commit, attemptId }, ...(relaxed ? { testTimeoutMs: GATE_TIMEOUT_RECHECK_MS } : {}) });
|
|
2004
|
-
const
|
|
2005
|
-
|
|
2252
|
+
partLimitSeconds, sourceBinding: { cutSha: commit, attemptId }, ...(rulesBaseSha ? { rulesBaseSha } : {}), ...(relaxed ? { testTimeoutMs: GATE_TIMEOUT_RECHECK_MS } : {}) });
|
|
2253
|
+
const warmEligible = files.length === 1 && pod.warmEligible === true && !!pod.warmReplay && !!pod.warmQuarantine && !!pod.warmFinalize
|
|
2254
|
+
&& pod.warmCutSha === commit;
|
|
2255
|
+
const warmRequest = warmEligible && pod.warmProducerEnabled && pod.warmReplayOwner !== false ? { protocol: 1 as const, runId: randomUUID(), attemptId, cutSha: commit,
|
|
2256
|
+
...(pod.warmBaselineSha ? { baselineSha: pod.warmBaselineSha } : {}), deadlineAt: Date.now() + timeoutMs + 60_000,
|
|
2257
|
+
files, envProfile: 'gate-v1' } : undefined;
|
|
2258
|
+
const replay = warmRequest ? await pod.warmReplay!(warmRequest) : undefined;
|
|
2259
|
+
const leased = replay?.command;
|
|
2260
|
+
let closed = false;
|
|
2261
|
+
const quarantine = async (reason: string) => {
|
|
2262
|
+
if (!leased || closed) return;
|
|
2263
|
+
await pod.warmQuarantine!(leased, reason);
|
|
2264
|
+
closed = true;
|
|
2265
|
+
};
|
|
2266
|
+
let producer: RunPodCommandOptions['warmProducer'];
|
|
2267
|
+
if (leased && replay?.transport.status === 'observed' && replay.transport.reason === 'complete') {
|
|
2268
|
+
if (warmCommandMatchesRequest(leased, warmRequest!) && leased.podName && leased.podUid
|
|
2269
|
+
&& leased.podContext && leased.image?.endsWith(`@${leased.key.imageDigest}`)) {
|
|
2270
|
+
producer = { pod: leased.podName, podUid: leased.podUid, context: leased.podContext, image: leased.image,
|
|
2271
|
+
commit, files: ['package.json', 'bun.lock'] };
|
|
2272
|
+
} else await quarantine('gate-producer-identity');
|
|
2273
|
+
}
|
|
2274
|
+
let job: PodCommandResult;
|
|
2275
|
+
try {
|
|
2276
|
+
job = await runPod({ pool: pod.pool, ...(poolOverride ? { poolScheduler: poolOverride } : {}), clone: true,
|
|
2277
|
+
provenanceGate: true,
|
|
2278
|
+
provenance: { actorKind: process.env.ELANOUS_SEAT ? 'seat' : 'loop', actor: process.env.ELANOUS_SEAT || 'release-loop',
|
|
2279
|
+
character: 'gate-shard-runner', launchHost: hostname(), entrance: 'release-recheck', kind: 'gate-recheck',
|
|
2280
|
+
run: process.env.ELANOUS_RUN_ID || attemptId, phase: 'release-gate', requestedAt: new Date().toISOString(),
|
|
2281
|
+
graphNode: 'gate-triage', commit, reason: `release gate recheck ${files.length} files`, pool: pod.pool,
|
|
2282
|
+
memReq: POD_ISOLATION_MEMORY_LIMIT, warmReused: !!producer },
|
|
2283
|
+
source: { kind: 'commit', sha: commit }, deadlineSeconds, ...(producer ? { warmProducer: producer, image: producer.image, imageCommit: commit } : {}),
|
|
2284
|
+
...(bunCache ? { bunCache } : {}), ...gateHostMirror(pod),
|
|
2006
2285
|
...(installSlots > 0 ? { installSlots: POD_INSTALL_SLOTS_HOST_PATH } : {}), ...(pod.cpu ? { cpu: pod.cpu } : {}),
|
|
2007
2286
|
memoryLimit: POD_ISOLATION_MEMORY_LIMIT, name: `gate-recheck-${randomUUID()}`, command: ['bash', '-lc', shell] });
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
}
|
|
2287
|
+
} catch (error) {
|
|
2288
|
+
await quarantine('gate-job-failed');
|
|
2289
|
+
throw error;
|
|
2290
|
+
}
|
|
2291
|
+
try {
|
|
2292
|
+
const artifact = (name: string) => {
|
|
2293
|
+
const path = join(job.artifactsDir, name);
|
|
2294
|
+
return existsSync(path) ? readFileSync(path, 'utf8') : undefined;
|
|
2295
|
+
};
|
|
2012
2296
|
const installed = artifact('install.rc')?.trim() === '0';
|
|
2013
|
-
const warmEligible = files.length === 1 && pod.warmEligible === true && !!pod.warmReplay && !!pod.warmQuarantine && !!pod.warmFinalize
|
|
2014
|
-
&& pod.warmCutSha === commit;
|
|
2015
2297
|
const incomplete = (message: string, file: string): Error => warmEligible
|
|
2016
2298
|
? new WarmRecheckIncomplete(message, file, { files: [file], attemptId, status: 'unmeasured', reason: 'reference-unmeasured', quarantined: 'no' })
|
|
2017
2299
|
: new Error(message);
|
|
@@ -2048,7 +2330,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2048
2330
|
}
|
|
2049
2331
|
debug.log('release-loop.gate', 'pod-isolated-recheck', { file, commit, job: job.job, rc: rc ?? 124, relaxed });
|
|
2050
2332
|
}
|
|
2051
|
-
if (warmEligible && pod.warmReplay && pod.warmQuarantine && pod.warmFinalize) {
|
|
2333
|
+
if (warmEligible && !pod.warmProducerEnabled && pod.warmReplay && pod.warmQuarantine && pod.warmFinalize) {
|
|
2052
2334
|
shadowDeadline = Date.now() + shadowTimeoutMs;
|
|
2053
2335
|
const first = results.get(files[0]!)!;
|
|
2054
2336
|
const reference = first.warmReference;
|
|
@@ -2124,11 +2406,26 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2124
2406
|
debug.log('release-loop.gate', 'warm-shadow-recheck', first.warmShadow);
|
|
2125
2407
|
}
|
|
2126
2408
|
}
|
|
2409
|
+
if (producer && leased) {
|
|
2410
|
+
const reference = results.get(files[0]!)?.warmReference;
|
|
2411
|
+
const decision = job.exitCode === 0 && reference && !timedOut
|
|
2412
|
+
? assessWarmWorkerShadow(leased, [reference], replay!.transport.reply) : null;
|
|
2413
|
+
if (decision?.reason !== 'match') await quarantine(decision?.reason ?? 'reference-unmeasured');
|
|
2414
|
+
else {
|
|
2415
|
+
await pod.warmFinalize!(leased);
|
|
2416
|
+
closed = true;
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2127
2419
|
return results;
|
|
2420
|
+
} catch (error) {
|
|
2421
|
+
await quarantine('gate-job-incomplete');
|
|
2422
|
+
throw error;
|
|
2423
|
+
}
|
|
2128
2424
|
};
|
|
2129
2425
|
return {
|
|
2130
2426
|
command,
|
|
2131
2427
|
localCommand: commandOverride && !remote ? commandOverride : localCommand,
|
|
2428
|
+
now: clock,
|
|
2132
2429
|
async recheckPod(file, commit, pod, timeoutMs, relaxed = false) {
|
|
2133
2430
|
return (await runPodRecheckBatch([file], commit, pod, timeoutMs, relaxed)).get(file)!;
|
|
2134
2431
|
},
|
|
@@ -2295,8 +2592,8 @@ export function saveBaselineFileCache(ledger: string, commit: string, rows: Base
|
|
|
2295
2592
|
} finally { if (existsSync(temp)) rmSync(temp); }
|
|
2296
2593
|
}
|
|
2297
2594
|
|
|
2298
|
-
export async function judgeGate(opts: GateOptions, runner: GateRunner = createGateRunner(opts.repo ?? process.cwd(), opts.remote)
|
|
2299
|
-
|
|
2595
|
+
export async function judgeGate(opts: GateOptions, runner: GateRunner = createGateRunner(opts.repo ?? process.cwd(), opts.remote),
|
|
2596
|
+
imageProbe: typeof preflightGateImageDigest = preflightGateImageDigest): Promise<GateResult> {
|
|
2300
2597
|
if (opts.pod?.warmReplay) opts = { ...opts, pod: { ...opts.pod,
|
|
2301
2598
|
warmShadowScope: { requested: false, attempts: 0 } } };
|
|
2302
2599
|
const explicitConfig = getElanousConfigDirOverride();
|
|
@@ -2307,6 +2604,10 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
2307
2604
|
debug.log('release.run', opts.forceFreeze ? 'freeze-forced' : 'frozen', { version: opts.version, reason: frozen.reason, until: frozen.until, node: 'gate' });
|
|
2308
2605
|
if (!opts.forceFreeze) throw new Error(landingFreezeMessage(frozen));
|
|
2309
2606
|
}
|
|
2607
|
+
// GATE-FLOW-SIM — the injected clock (absent = `Date.now`), read only after the freeze check: a frozen gate must not
|
|
2608
|
+
// touch its runner at all (a refusal is decided before any side effect).
|
|
2609
|
+
const clock = runner.now ?? Date.now;
|
|
2610
|
+
const start = clock();
|
|
2310
2611
|
const ledger = explicitConfig ? root : (opts.ledgerRoot ?? releaseLedgerRoot());
|
|
2311
2612
|
const roots = [...new Set([ledger, root])];
|
|
2312
2613
|
const cachedBaseline = (version: string, commit: string) => {
|
|
@@ -2340,6 +2641,8 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
2340
2641
|
|| (opts.baselineVersion && !versionPattern.test(opts.baselineVersion))
|
|
2341
2642
|
|| (opts.baselineCommit && !sha.test(opts.baselineCommit))) throw new Error('invalid commit or version');
|
|
2342
2643
|
if (!opts.baselineVersion) throw new Error('previous release version required (--baseline-version)');
|
|
2644
|
+
if (opts.rehearsalBaseline && (opts.graphRunId || !opts.pod || !/-rc\.\d+$/.test(opts.version)))
|
|
2645
|
+
throw new Error('rehearsal baseline requires a direct Pod gate with an isolated rc version');
|
|
2343
2646
|
// Observation only. Failure to plan cannot change the gate selector or verdict.
|
|
2344
2647
|
try { radiusShadow = planGateRadiusShadow({ repo, ledgerRoot: ledger, version: opts.version,
|
|
2345
2648
|
baselineVersion: opts.baselineVersion, forCommit: opts.commit, instanceRoot: root,
|
|
@@ -2388,12 +2691,31 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
2388
2691
|
for (const dir of [cutTree, join(cutTree, 'apps/pwa')]) check(await runner.command('bun', ['install'], dir), `bun install ${dir}`);
|
|
2389
2692
|
// GATE-PARTIAL: a plan made by `release resume --from gate --partial` for exactly this commit re-runs only its files.
|
|
2390
2693
|
// Its logs go to cut-partial/ — the full cut/ logs stay whole (the next release reads them as a baseline source).
|
|
2391
|
-
const
|
|
2392
|
-
|
|
2694
|
+
const partialRead = readPartialPlanResult(roots, opts.version, opts.commit);
|
|
2695
|
+
const explicitPartial = partialRead.plan;
|
|
2696
|
+
const rehearsalStampPresent = ['hard-5h', 'hard-mid'].some((step) =>
|
|
2697
|
+
existsSync(join(root, 'release', opts.version, 'rehearsal', `${step}.json`)));
|
|
2698
|
+
const currentImage = !explicitPartial && opts.graphRunId && opts.pod && rehearsalStampPresent
|
|
2699
|
+
? imageProbe(opts.pod.pool) : null;
|
|
2700
|
+
const radius = !explicitPartial && opts.graphRunId && opts.pod && rehearsalStampPresent
|
|
2701
|
+
? planHardRehearsalRadius({ root, repo, version: opts.version, forCommit: opts.commit,
|
|
2702
|
+
graphPins: gateGraphPins(root, opts.graphRunId), envFingerprint: gateEnvFingerprint(process.env), imageDigest: currentImage })
|
|
2703
|
+
: null;
|
|
2704
|
+
if (radius) debug.log('release-loop.gate', 'radius-live-plan', { version: opts.version, reason: radius.reason,
|
|
2705
|
+
files: radius.plan?.files.length ?? null, rehearsalRunId: radius.rehearsalRunId ?? null });
|
|
2706
|
+
let partial = explicitPartial ?? radius?.plan ?? null;
|
|
2707
|
+
debug.log('release-loop.gate', partial ? 'partial-regate' : 'partial-fallback', {
|
|
2708
|
+
version: opts.version, priorCommit: partial?.priorCommit ?? null, commit: opts.commit,
|
|
2709
|
+
planKind: partial?.kind ?? 'full', remeasureFileCount: partial?.files.length ?? 0,
|
|
2710
|
+
preservedShardCount: partial?.kind === 'recheck' && 'preservedShardCount' in partial
|
|
2711
|
+
&& typeof partial.preservedShardCount === 'number' ? partial.preservedShardCount : 0,
|
|
2712
|
+
fallbackReason: partial ? undefined : partialRead.fallbackReason,
|
|
2713
|
+
});
|
|
2393
2714
|
if (partial?.kind === 'carry') {
|
|
2715
|
+
const carryPriorCommit = partial.priorCommit;
|
|
2394
2716
|
// The plan alone is not evidence: the old cut and its verdict must still be complete and clean at this SHA.
|
|
2395
|
-
if (!verifyDocsOnlyCarry(repo,
|
|
2396
|
-
throw new Error(`carry plan diff is not docs-only: ${
|
|
2717
|
+
if (!verifyDocsOnlyCarry(repo, carryPriorCommit, opts.commit))
|
|
2718
|
+
throw new Error(`carry plan diff is not docs-only: ${carryPriorCommit}..${opts.commit}`);
|
|
2397
2719
|
const trusted = roots.some((location) => {
|
|
2398
2720
|
const dir = join(location, 'release', opts.version);
|
|
2399
2721
|
const failuresPath = join(dir, 'gate-failures.json');
|
|
@@ -2401,11 +2723,11 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
2401
2723
|
if (!existsSync(failuresPath) || !existsSync(verdictPath)) return false;
|
|
2402
2724
|
const failures = JSON.parse(readFileSync(failuresPath, 'utf8')) as { commit?: unknown; failures?: unknown; errors?: unknown };
|
|
2403
2725
|
const verdict = JSON.parse(readFileSync(verdictPath, 'utf8')) as { commit?: unknown; outcome?: unknown };
|
|
2404
|
-
return failures.commit ===
|
|
2726
|
+
return failures.commit === carryPriorCommit && Array.isArray(failures.failures) && failures.failures.length === 0
|
|
2405
2727
|
&& (failures.errors === undefined || (Array.isArray(failures.errors) && failures.errors.length === 0))
|
|
2406
|
-
&& verdict.commit ===
|
|
2728
|
+
&& verdict.commit === carryPriorCommit && verdict.outcome === 'ok';
|
|
2407
2729
|
});
|
|
2408
|
-
if (!trusted) throw new Error(`carry plan has no complete clean gate at ${
|
|
2730
|
+
if (!trusted) throw new Error(`carry plan has no complete clean gate at ${carryPriorCommit}`);
|
|
2409
2731
|
}
|
|
2410
2732
|
// GATE-SKIP-KNOWN-RETRY — the ids the verdict will not count as introduced (the recorded baseline ⊕ env-known), read
|
|
2411
2733
|
// before the sweep so a shard failing only with them skips its isolation retries. No recorded baseline = no skip.
|
|
@@ -2420,12 +2742,30 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
2420
2742
|
} catch (error) { debug.log('release-loop.gate', 'known-failures-unavailable', { error: String(error) }); }
|
|
2421
2743
|
}
|
|
2422
2744
|
const durationSource = join(ledger, 'release', opts.baselineVersion, 'gate-logs', 'cut');
|
|
2423
|
-
const
|
|
2745
|
+
const runCut = (selected: typeof partial) => selected?.kind === 'carry' ? undefined : runner.sweep(cutTree,
|
|
2746
|
+
join(root, 'release', opts.version, 'gate-logs', selected ? 'cut-partial' : 'cut'),
|
|
2424
2747
|
opts.pod ? { ...opts.pod, durationSource, ...(knownFailures.length ? { knownFailures } : {}), onSelectionTimed: ({ ms }) => { result.selectionMs = ms; },
|
|
2748
|
+
// GATE-RISK-LANE — the watch list is read from this gate's own ledgers (the instance root ⊕ the machine ledger);
|
|
2749
|
+
// the cut sweep puts those files first, the baseline sweep keeps its plan.
|
|
2750
|
+
riskWatch: { instanceRoots: roots, ledgerRoots: roots },
|
|
2425
2751
|
// GATE-LIVE-OBS: the release ledger's shards.json (UX #24782) — never the run tree's universe; an explicit
|
|
2426
2752
|
// --ledger-root (tests) keeps it in that ledger.
|
|
2427
2753
|
shardsFile: { path: opts.ledgerRoot ? gateShardsPath(opts.version, opts.ledgerRoot) : gateShardsPath(opts.version), version: opts.version } } : undefined,
|
|
2428
|
-
|
|
2754
|
+
selected?.files);
|
|
2755
|
+
let cutRun = await runCut(partial);
|
|
2756
|
+
const radiusSelectionMatched = partial?.files && cutRun?.podSweep?.files
|
|
2757
|
+
&& JSON.stringify([...partial.files].sort()) === JSON.stringify([...cutRun.podSweep.files].sort());
|
|
2758
|
+
if (radius?.plan && (!radiusSelectionMatched
|
|
2759
|
+
|| !partialGateImagesMatch(root, opts.version, currentImage!, opts.commit, cutRun?.podSweep))) {
|
|
2760
|
+
debug.log('release-loop.gate', 'radius-live-image-drift', { version: opts.version,
|
|
2761
|
+
rehearsalRunId: radius.rehearsalRunId, action: 'full-sweep' }, { level: 'warn' });
|
|
2762
|
+
partial = null;
|
|
2763
|
+
cutRun = await runCut(null);
|
|
2764
|
+
}
|
|
2765
|
+
if (radius?.plan && partial === radius.plan) debug.log('release-loop.gate', 'baseline-consumed', {
|
|
2766
|
+
version: opts.version, baselineCommit: radius.plan.priorCommit, commit: opts.commit,
|
|
2767
|
+
selectedFiles: radius.plan.files.length, rehearsalRunId: radius.rehearsalRunId,
|
|
2768
|
+
});
|
|
2429
2769
|
cutSweepRc = cutRun?.rc ?? null;
|
|
2430
2770
|
cutSweepUsable = !!cutRun && (cutRun.rc === 0 || cutRun.rc === 1) && !cutRun.timedOut
|
|
2431
2771
|
&& !cutRun.abortedShards && !cutRun.stalledEnv?.length;
|
|
@@ -2621,7 +2961,7 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
2621
2961
|
debug.log('release-loop.gate', 'isolated-recheck-plan', { files: recheckFiles.length, window: recheckLimit,
|
|
2622
2962
|
concurrency: opts.pod && runner.recheckPod ? recheckConcurrency : 1, executor: opts.pod && runner.recheckPod ? 'pod' : 'host',
|
|
2623
2963
|
remote: opts.remote ?? null, podBatchFiles: opts.pod && runner.recheckPod && runner.recheckPodBatch ? GATE_RECHECK_POD_BATCH_FILES : 1 });
|
|
2624
|
-
const recheckStartedAt =
|
|
2964
|
+
const recheckStartedAt = clock();
|
|
2625
2965
|
const recordWarmShadow = (shadow?: WarmShadowObservation) => {
|
|
2626
2966
|
if (shadow && !result.warmShadow?.some((entry) => entry.attemptId === shadow.attemptId))
|
|
2627
2967
|
(result.warmShadow ??= []).push(shadow);
|
|
@@ -2784,6 +3124,11 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
2784
3124
|
result.introduced.sort();
|
|
2785
3125
|
preexistingIds.sort();
|
|
2786
3126
|
result.loadFlaky?.sort();
|
|
3127
|
+
// GATE-RISK-LANE — did the shard that ran first carry this gate's new failures? Observation only: the outcome above
|
|
3128
|
+
// is the existing aggregation, and the counts (introduced · preexisting) are the same ones the verdict writes.
|
|
3129
|
+
if (cutRun?.riskLane) debug.log('release-loop.gate', 'risk-lane-introduced', { files: cutRun.riskLane.files,
|
|
3130
|
+
plannedSeconds: cutRun.riskLane.plannedSeconds, durationMs: cutRun.riskLane.durationMs, rc: cutRun.riskLane.rc,
|
|
3131
|
+
introduced: result.introduced.filter((id) => cutRun!.riskLane!.files.includes(fileOf(id))).length });
|
|
2787
3132
|
result.preexisting = preexistingIds.length;
|
|
2788
3133
|
result.baselineCache = cacheStats;
|
|
2789
3134
|
debug.log('release.gate', 'baseline-cache', { hits: cacheStats.hits, misses: cacheStats.misses, commit: baseSha });
|
|
@@ -2816,6 +3161,8 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
2816
3161
|
if (error instanceof StalledPodShards) {
|
|
2817
3162
|
result.stalledShards = error.stalledShards;
|
|
2818
3163
|
result.partialSummary = error.partialSummary;
|
|
3164
|
+
result.partialFailures = error.partialFailures;
|
|
3165
|
+
result.preservedShardCount = error.preservedShardCount;
|
|
2819
3166
|
if (error.abortedShards) result.abortedShards = error.abortedShards;
|
|
2820
3167
|
}
|
|
2821
3168
|
} finally {
|
|
@@ -2834,7 +3181,7 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
2834
3181
|
} catch (error) { result.outcome = 'error'; result.error = `cleanup: ${String(error)}`; }
|
|
2835
3182
|
}
|
|
2836
3183
|
if (opts.remote) runner.remoteMirror = undefined;
|
|
2837
|
-
result.durationMs =
|
|
3184
|
+
result.durationMs = clock() - start;
|
|
2838
3185
|
}
|
|
2839
3186
|
if (cutFailures && !result.stalledShards?.length && !result.stalledEnv.length && !result.unmeasuredRecheckFiles?.length) {
|
|
2840
3187
|
try {
|
|
@@ -2866,6 +3213,13 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
2866
3213
|
};
|
|
2867
3214
|
const observed = { ...(result.selectionMs !== undefined ? { selectionMs: result.selectionMs } : {}),
|
|
2868
3215
|
...(result.warmShadow?.length ? { warmShadow: result.warmShadow } : {}) };
|
|
3216
|
+
// Receipt is advisory for a later radius gate. Missing identity makes reuse invalid, never this gate's verdict.
|
|
3217
|
+
const baselineReceipt = gateBaselineReceipt({ root, version: opts.version, commit: opts.commit,
|
|
3218
|
+
graphPins: gateGraphPins(root, opts.graphRunId) ?? (opts.rehearsalBaseline ? gateSourcePins(repo, opts.commit) : null),
|
|
3219
|
+
envFingerprint: gateEnvFingerprint(process.env),
|
|
3220
|
+
fullSweep: result.outcome === 'ok' && cutSweepUsable && !result.partial && !!cutFailures
|
|
3221
|
+
&& !result.rcNonzeroNoFailures.length && !result.stalledShards?.length && !result.stalledEnv.length
|
|
3222
|
+
&& !result.unmeasuredRecheckFiles?.length && !result.unmeasuredErrors?.length });
|
|
2869
3223
|
try {
|
|
2870
3224
|
for (const location of roots) {
|
|
2871
3225
|
const path = verdictFile(location, opts.version);
|
|
@@ -2878,7 +3232,7 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
2878
3232
|
try {
|
|
2879
3233
|
const warmHistory = readWarmVerdictHistory(path) ?? [];
|
|
2880
3234
|
writeFileSync(temp, JSON.stringify({ version: opts.version, commit: opts.commit,
|
|
2881
|
-
outcome: result.outcome, ...ids, ...observed,
|
|
3235
|
+
outcome: result.outcome, ...ids, ...observed, baselineReceipt,
|
|
2882
3236
|
...(opts.pod?.warmReplay ? { baselineCommit, warmHistory } : {}) }, null, 2) + '\n', { mode: 0o600 });
|
|
2883
3237
|
chmodSync(temp, 0o600);
|
|
2884
3238
|
renameSync(temp, path);
|
|
@@ -2894,7 +3248,7 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
2894
3248
|
try {
|
|
2895
3249
|
const warmHistory = readWarmVerdictHistory(path) ?? [];
|
|
2896
3250
|
writeFileSync(temp, JSON.stringify({ version: opts.version, commit: opts.commit,
|
|
2897
|
-
outcome: result.outcome, ...ids, ...observed,
|
|
3251
|
+
outcome: result.outcome, ...ids, ...observed, baselineReceipt,
|
|
2898
3252
|
...(opts.pod?.warmReplay ? { baselineCommit, warmHistory } : {}) }, null, 2) + '\n', { mode: 0o600 });
|
|
2899
3253
|
chmodSync(temp, 0o600);
|
|
2900
3254
|
renameSync(temp, path);
|
|
@@ -3023,6 +3377,7 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
|
|
|
3023
3377
|
const arg = args[i];
|
|
3024
3378
|
if (arg === '--help') return 'help';
|
|
3025
3379
|
if (arg === '--json') continue;
|
|
3380
|
+
if (arg === '--rehearsal-baseline') { opts.rehearsalBaseline = true; continue; }
|
|
3026
3381
|
// 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).
|
|
3027
3382
|
if (arg === '--fresh-shards') { freshShards = true; continue; }
|
|
3028
3383
|
if (arg === '--rehearsal-tui') { opts.rehearsalTui = true; continue; }
|
|
@@ -3085,7 +3440,7 @@ export async function runGateNode(argv: string[], env: NodeJS.ProcessEnv, runner
|
|
|
3085
3440
|
debug.log('release-loop.gate', 'executor', { version: opts.version, ...placement, requested: opts.executor ?? null });
|
|
3086
3441
|
}
|
|
3087
3442
|
if (opts === 'help') {
|
|
3088
|
-
console.log('Usage: bun scripts/release-loop/gate-node.ts --commit <sha> --version <v> [--baseline-version <prev>] [--baseline-commit <sha>] [--remote <ssh-host>] [--remote-mirror <path>] [--pod-pool <pool>] [--pod-shards <n>] [--pod-shard-timeout-seconds <n>] [--recheck-file-limit <n>] [--fresh-shards] [--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).');
|
|
3443
|
+
console.log('Usage: bun scripts/release-loop/gate-node.ts --commit <sha> --version <v> [--baseline-version <prev>] [--baseline-commit <sha>] [--remote <ssh-host>] [--remote-mirror <path>] [--pod-pool <pool>] [--pod-shards <n>] [--pod-shard-timeout-seconds <n>] [--recheck-file-limit <n>] [--fresh-shards] [--executor <pod|host>] [--rehearsal-baseline] [--json]\nWithout flags, input.commit, input.version and input.previousVersion come from the JSON file at ELANOUS_GRAPH_CONTEXT.\n--executor host (input.gateExecutor) ignores every pod option and sweeps on --remote (input.gateRemote, required).\n--rehearsal-baseline pins source graph bytes only for a direct Pod gate on an isolated rc version.');
|
|
3089
3444
|
return 0;
|
|
3090
3445
|
}
|
|
3091
3446
|
version = opts.version;
|
|
@@ -3098,7 +3453,7 @@ export async function runGateNode(argv: string[], env: NodeJS.ProcessEnv, runner
|
|
|
3098
3453
|
throw new Error('release gate host reservation requires graph runId and pinned state root');
|
|
3099
3454
|
const releaseHosts = context && hosts.length
|
|
3100
3455
|
? reserveReleaseGateHosts(reservationRoot!, context.runId!, hosts) : () => undefined;
|
|
3101
|
-
try { result = await judge(opts, runner); }
|
|
3456
|
+
try { result = await judge(context?.runId ? { ...opts, graphRunId: context.runId } : opts, runner); }
|
|
3102
3457
|
finally { releaseHosts(); }
|
|
3103
3458
|
} catch (error) {
|
|
3104
3459
|
result = { outcome: 'error', commit: '', introduced: [], preexisting: 0, fixed: 0, knownEnv: 0, knownEnvCleared: [], stalledEnv: [], rcNonzeroNoFailures: [], durationMs: Date.now() - start, error: String(error) };
|