elanous 0.2.35 → 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/{d06f9160.a3d9062f95de8ad7.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/{4346.8601952e73bf5803.js → 1201.afbfc5b1c5e5cbe4.js} +1 -1
- 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/{3103.0df8da390948bb84.js → 2500.cbc1257b774aae52.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{7644.17e37ba6f1d4226e.js → 2683.c9773a0c589b031b.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/2709.8e1889034255fc5f.js +24 -0
- package/apps/pwa/out/_next/static/chunks/{4718-17d2af636796a330.js → 275-02ea4c2a33980422.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/2812ca6a.3b9f4a7a08885744.js +1 -0
- package/apps/pwa/out/_next/static/chunks/284.ac77c0db55bfd3a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8714.ff0b7ce3ec3e3ae2.js → 2941.3c3546f96bc6efa7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2976.b000698a3c6512dc.js +24 -0
- package/apps/pwa/out/_next/static/chunks/{9214.63a510d39178e997.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/3136.30aaaa5fa03662a3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3677-4215f826f6aa68e5.js → 3148-98efd4869d660a87.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3155.2587a55196ecb394.js +226 -0
- 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/3324.a09703033abcbd2f.js +34 -0
- 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/3538.a445e50bb359d068.js +32 -0
- 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/{9512.5a76207a730e0491.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/{8557-e7898769499cd56f.js → 3757-180308bc61021b05.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/379.ee262e37586fe1ae.js +74 -0
- package/apps/pwa/out/_next/static/chunks/{359-2250789a2d3499e7.js → 3840-5825d56d46976a92.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3969-81054381eaec4c4c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{da5176c6.6773b6c5994e1d99.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/{9952.5daf5f94063a2b3e.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/{938.66ad1ef0e5abae1c.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/5085-bce4904931767ca6.js +1 -0
- 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/{6236.e18cced2d5ba6afb.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/{3408.21d1570602f4b6c0.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/5711.957b095705c12560.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1530.0e612369b6fd7ea0.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/599.c39c571b6733f2a2.js +41 -0
- 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/6925.562d532110879886.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7385.50a308a477bdfc21.js → 6943.c6b24124853a47b2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{630.fe5a46428d63b013.js → 727.7deb87749b0a343a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{2018.a741297bce759490.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/{8444.4c26b379ca2b9596.js → 771.c3cdb6c1f7f47c02.js} +17 -17
- 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/{3589.5444a06bcf6e775c.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/{b2f6ba4d.fb56fdbbf798df33.js → 873d6c12.09bd499f57171543.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{5101.e32282718f99ed6f.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/{5637.281e5dec4ea6ecf1.js → 914.8abefd48d579547b.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/9187-c87a2207d4ac5d46.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{4705-194e64e47ca0fbdb.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/{8637.47f5b35afcae5e6b.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/{1674.2a65fd5668e1cc5f.js → 9789.60f723d18097ec30.js} +1 -1
- 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-083747f1d4518f61.js +1 -0
- 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/{0368f25d-ad84af92fefee68f.js → ccae1c4b-0cf414f5eff1b10d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{cbe38a15.5a032598e39970c6.js → ceba0382.650712b56b85b582.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{b6705a4f-b9a9047de9eecfd0.js → da5ecc5c-b005ad8cac737d57.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{56d372f2.681ab4ec4c998fae.js → f9a61e4d.1fc2c1dbdd49fe46.js} +1 -1
- 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/{T1mBQqmkwKDLGtyisDCOd → 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/docs/goal-context/05-authoring-lessons.jsonl +47 -0
- package/docs/goal-context/README.md +3 -0
- package/graphs/default-loop.yaml +6 -1
- package/graphs/implement-loop.yaml +24 -0
- package/graphs/release/recipes.yaml +6 -0
- package/graphs/release/release-loop.yaml +17 -4
- 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 +1 -1
- package/scripts/first10-gate.ts +416 -0
- package/scripts/fleet/wsl-node-setup.sh +432 -0
- package/scripts/release-loop/docs-node.ts +204 -8
- package/scripts/release-loop/gate-node.ts +249 -53
- package/scripts/release-loop/node-verdict.ts +2 -0
- package/scripts/release-loop/resume-cap.ts +14 -34
- package/scripts/release-loop/resume-release.ts +37 -11
- package/scripts/release-loop/traffic-node.ts +59 -0
- package/scripts/release-loop/traffic-transition.ts +159 -0
- package/scripts/rhythm/daily.ts +137 -3
- package/scripts/ux-sim.ts +53 -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/task-notification.ts +56 -10
- package/src/agent/types.ts +4 -0
- package/src/agent-substrate/pr-reviewer.ts +55 -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 +168 -16
- package/src/autopilot/mission-codebase-gate.ts +141 -12
- package/src/autopilot/pr-manager.ts +25 -5
- package/src/boot/daemon-tools/self-implement.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 -4
- package/src/chat/internal-turn-origin.ts +1 -0
- package/src/chat/paste-fold.ts +86 -0
- 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/config-audit.ts +119 -0
- package/src/cli/doctor-cli.ts +22 -0
- package/src/cli/guardian-cli.ts +10 -0
- package/src/cli/herdr-cli.ts +34 -0
- package/src/cli/light-entry-route.ts +6 -3
- 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/pr-cli.ts +29 -16
- 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-recovery.ts +117 -1
- package/src/cli/pty-takeover-cli.ts +8 -7
- package/src/cli/release-cli.ts +21 -5
- package/src/cli/release-single-flight.ts +1 -1
- package/src/cli/tasks-card-show.ts +162 -0
- package/src/cli/tasks-cli.ts +574 -356
- package/src/cli/tasks-read-cli.ts +197 -0
- 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/context/keys.ts +8 -31
- package/src/dashboard/index.ts +202 -49
- 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/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/standalone-log-sink.ts +15 -2
- package/src/esc-abort-gate.ts +69 -0
- package/src/git-fs/worktree.ts +95 -1
- package/src/graph-runner/graph-cli.ts +18 -0
- package/src/graph-runner/runner.ts +20 -11
- package/src/graph-wizard/generate.ts +108 -4
- package/src/graph-wizard/steps.ts +15 -0
- package/src/grounding/proactive-ground.ts +86 -0
- package/src/harness/control-inbox.ts +49 -1
- package/src/harness/harness-cli-command.ts +51 -7
- package/src/harness/harness-incidents.ts +52 -1
- 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/index.ts +327 -12
- package/src/input-core/context-key-runtime.ts +21 -0
- package/src/light-cli.ts +6 -1
- package/src/llm-wellformed.ts +70 -0
- package/src/llm.ts +35 -4
- 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/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/nexus/api/decisions-route.ts +38 -18
- package/src/nexus/api/graph-wizard-api.ts +20 -2
- package/src/nexus/boot/register-mcp-clients.ts +263 -38
- package/src/nexus/onboarding/first-boot-wizard.ts +56 -8
- package/src/onboarding/first-run.ts +37 -6
- package/src/onboarding/web-first.ts +43 -11
- package/src/ops/overview-collect.ts +12 -6
- package/src/ops/overview.ts +3 -3
- package/src/persona/loader.ts +11 -0
- package/src/persona/types.ts +8 -0
- 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-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 +76 -4
- package/src/release-loop/rehearsal.ts +36 -20
- package/src/repl/index.ts +19 -1
- package/src/seat-loop/knowhow-lint.ts +1 -1
- package/src/self-dev/decompose-proposal.ts +172 -20
- package/src/self-dev/decompose.ts +61 -0
- package/src/self-dev/dev-cli.ts +37 -3
- package/src/self-dev/dev-pipeline.ts +8 -2
- package/src/self-dev/draft-triage-rules.ts +31 -0
- package/src/self-dev/glm-credential-guard.ts +77 -0
- package/src/self-dev/graph-journey-nodes.ts +1 -0
- package/src/self-dev/graph-journey-route.ts +12 -2
- package/src/self-dev/orchestrate-cli.ts +68 -3
- package/src/self-dev/orchestrate.ts +116 -17
- package/src/self-dev/run-supervisor.ts +55 -2
- package/src/self-dev/run-types.ts +1 -1
- package/src/self-health/autoheal.ts +153 -0
- package/src/self-health/signatures.ts +6 -0
- package/src/self-implement/device-first.ts +116 -0
- package/src/self-implement/doctor-visit.ts +239 -1
- package/src/self-implement/evidence-command-budget.ts +80 -0
- package/src/self-implement/goal-author-cli.ts +1 -0
- package/src/self-implement/goal-author-decompose.ts +265 -4
- package/src/self-implement/goal-author.ts +33 -8
- package/src/self-implement/goal-elaborate.ts +176 -22
- package/src/self-implement/goal-refine.ts +3 -0
- 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 +222 -21
- 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-policy.ts +30 -0
- package/src/self-implement/run-ledger.ts +108 -0
- package/src/self-implement/run-ttl.ts +38 -24
- package/src/self-implement/scope-adjacency.ts +3 -1
- package/src/self-implement/seams.ts +60 -4
- package/src/self-implement/wiring-check.ts +358 -0
- package/src/shell-env-bootstrap.ts +11 -4
- package/src/skills/runner.ts +4 -2
- package/src/skills/tools/agent.ts +290 -48
- 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/rejudge-tick.ts +10 -2
- package/src/steward/ta-repair-event-collect.ts +17 -6
- package/src/task-agent/actions.ts +3 -1
- package/src/task-agent/land-queue.ts +21 -8
- package/src/task-agent/live-moves.ts +22 -6
- package/src/task-agent/mission.ts +30 -0
- 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 +41 -3
- package/src/task-agent/task-card-prefix.ts +2 -0
- package/src/task-agent/task-hand.ts +477 -22
- package/src/task-agent/task-parent-role-llm.ts +36 -0
- package/src/task-agent/tier-split.ts +78 -9
- package/src/task-orchestrator/surfaces/pod-account-broker.ts +9 -0
- package/src/task-orchestrator/surfaces/pod-child-reuse.ts +146 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +86 -5
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +215 -3
- package/src/task-orchestrator/surfaces/pod-launch-history.ts +133 -0
- package/src/task-orchestrator/surfaces/pod-manifest-test-normalize.ts +11 -0
- package/src/task-orchestrator/surfaces/pod-pool.ts +76 -4
- package/src/task-orchestrator/surfaces/pod-provenance.ts +8 -2
- 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-warm-worker-protocol.ts +4 -0
- package/src/task-orchestrator/surfaces/pod-warm-worker-provider.ts +485 -36
- package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +3 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +396 -19
- package/src/task-orchestrator/surfaces/self-implement.ts +42 -2
- package/src/tool-runtime/index.ts +15 -2
- package/src/tool-runtime/mcp-proxy-bootstrap.ts +29 -0
- package/src/tty-foreground.ts +155 -0
- package/src/tui/chat/goal-interim.ts +173 -0
- package/src/tui/chat/log-view.ts +205 -18
- package/src/tui/chat/self-health-status.ts +232 -0
- package/src/tui/entry.ts +370 -81
- package/src/tui/input/composer.ts +8 -1
- package/src/tui/slash/compact-command.ts +7 -0
- package/src/tui/slash/compact.ts +1 -7
- package/src/tui/slash/resume.ts +75 -0
- package/src/tui-client/local-chat-turn.ts +45 -2
- 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/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 +3 -2
- package/src/ux-sim/fake-bin.ts +45 -0
- 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 +102 -1
- package/src/ux-sim/fault-scope-escape-lift.ts +33 -6
- 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/1165.bad13fc9151a7288.js +0 -1
- package/apps/pwa/out/_next/static/chunks/13.02a07436dec15f89.js +0 -32
- package/apps/pwa/out/_next/static/chunks/1540.af38731fa7c4a3e0.js +0 -41
- package/apps/pwa/out/_next/static/chunks/1850.f2b470ba5a471da0.js +0 -24
- package/apps/pwa/out/_next/static/chunks/193.62da94cd0b88323b.js +0 -74
- package/apps/pwa/out/_next/static/chunks/1975.260f6d249fac5f7c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1982-67cd8dfe8682325f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1993-c0007b7ad0c0ef41.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1999-bb415351c8a2846c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1c735de4.f993a30bbd3c57c4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1dc5a567.9b092bfae3d7099b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2085.8f950b4bda1cbbc7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2159-6179a961376956b9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/216.b5c2a421aa1d28a5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2251.49fbe369f74c372c.js +0 -148
- package/apps/pwa/out/_next/static/chunks/2286-2939577360d3dcc0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2625.5d870aa999f33db1.js +0 -34
- package/apps/pwa/out/_next/static/chunks/2668.34d21539e62bf111.js +0 -77
- package/apps/pwa/out/_next/static/chunks/2833-0fe47af99ed13c29.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2930.d0edb4f108787ef7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2973.77318e7d85f59477.js +0 -62
- package/apps/pwa/out/_next/static/chunks/3123.2bee9ba15d320bd2.js +0 -90
- package/apps/pwa/out/_next/static/chunks/3162-6cf652bf7124d8cf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3577-7340009f7007a795.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3618.f514792d6c59d7fd.js +0 -36
- package/apps/pwa/out/_next/static/chunks/3669.343f9119aa276001.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3736.c2efad489604cc3b.js +0 -79
- package/apps/pwa/out/_next/static/chunks/3817.f9aeab2bb6675d73.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3f7c4ee5.3dde54f01a816613.js +0 -122
- package/apps/pwa/out/_next/static/chunks/4182-0b9b1251c29cc0cf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4441-875b97f5821ed58a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4582.bd68683c076649e3.js +0 -286
- package/apps/pwa/out/_next/static/chunks/4605-6e623f55aec77422.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4607.5cc27468628eb4bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4627.f07508b3936fd6c0.js +0 -24
- package/apps/pwa/out/_next/static/chunks/4747-c22262c093e46807.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4775-178215aed0daba61.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4815-6ffb8c3c991cc45d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4834-beca8b47ad29a898.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5023-fb4802b6d2492b02.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5154.a118f3957ed14e36.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5210-a2e0015e3d4d5d9c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5356.84ac712cc15928bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5401.22352141b6fd336e.js +0 -23
- package/apps/pwa/out/_next/static/chunks/5483-dd5d7697f6bdb106.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5662.02eaa029af05ad46.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5760.22456f433c6f42c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5852.db1f7449f4f74612.js +0 -287
- package/apps/pwa/out/_next/static/chunks/5891-0f11a7d7362a9072.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5943.264d14a44ca32513.js +0 -1
- package/apps/pwa/out/_next/static/chunks/604-1b3c39df083c02aa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6121-5f97072330738caf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6193.07d8ca5af4b09086.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6203-b3fea3a60d69ff14.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6286.d638fb5953e2e757.js +0 -8
- package/apps/pwa/out/_next/static/chunks/6301.ae0245f507e4395e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6673.fbbe5699f6ea5991.js +0 -27
- package/apps/pwa/out/_next/static/chunks/6777-f6d301efb5de828d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6786.423f24c88de034c7.js +0 -93
- package/apps/pwa/out/_next/static/chunks/6854-cef64a948c5e122d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6866-79aa5b4e47cac6aa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6b89c8bd.24df687a820500b3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7037-fe27f001db30d323.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7080.2d826860534fd88e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7118.8731be28c3ebb14b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7196.44241bc1df3e26fe.js +0 -149
- package/apps/pwa/out/_next/static/chunks/7235.c7c4e6cb5c693c2d.js +0 -104
- package/apps/pwa/out/_next/static/chunks/7706-58bfb1546637f217.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7839.5b91455338c9fd3f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7857-31f13ee96f4011aa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/81.f74cb60312fe4001.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8219.baa715d1deee80e2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8256.e3ac8421a71f82f4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8310-444a6a895c402f12.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8423.f7dda8aca4fcaae5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8437-8d5492094b166cfa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8496.75649af8652c74f4.js +0 -226
- package/apps/pwa/out/_next/static/chunks/8576.65bb654b524f3614.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8609.558ce2f8f9aea2be.js +0 -64
- package/apps/pwa/out/_next/static/chunks/8614.caec96cfb91f4ae7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8732.367f44a916202353.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9141-7fd1f73a5400bdc2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/921.c4267d0d69a65404.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9261.3962c3321b047691.js +0 -115
- package/apps/pwa/out/_next/static/chunks/936-26efd1a0efdd44d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9627-4b9efe616b27e2e2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/967-2aa39b2d04b4c690.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9734.40eb1e558c29ccea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9908.3aa54f7a586a73dc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9954-ca8d29752bdbb45a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9974-507fa67cb94066dd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-ce9e00f7fb45506a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-fa59d6d17daa3389.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-53f68bbe7bd4b774.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-1c500fbb9f4a7a04.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-99d72673aa5c307c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-d2d5adc1e9484c88.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-c607c5aae637f1cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-fa8c23596d555c0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-bdab8c5e855c8416.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-406b47745b53f269.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-a4276386b7051f50.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-8a26bab9ad375f25.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-775f48aa1dc7a2d5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-1c4dd5f959d0c558.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-c48938a8377c9f70.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-ce9e00f7fb45506a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-c6e7905859425aa9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-c280f4252452e830.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-cf8c57498f20970a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-ca3a17caf409fc83.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-26e6c18a1a750cf6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-3abc872cc6aeee39.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-f7481b7b2174fe3d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1ae7ef8077680337.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-47c5d4db587266bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-415f1e243c7ccb13.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-73f380f17b76f95c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-2991073ad66ee2d9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-1f5a60f70cb4dd0c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fbad60d1e358fb2c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-bac7dee94e5eae4a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-398047d6949e8ca5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-2d33a2231540ca89.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-5bf1e6bf70bcb96c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-ba59c19376a0917d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-111f187139add30f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-acaae197d5be5779.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-0df20b0f546a0d7d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-ce9e00f7fb45506a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-27e1202d24b8fe35.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-bda78c50a78363e5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-da592691293d9c9f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-f832994317a2e9f0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-dc0a532f5b9196ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-cfccbbeae0473404.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-527e2073af84c81b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-bac86e9112671357.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6d59dff410e9d76a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-53a61f8be09f02d2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-db8f559e763c17f9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c02aa4cc66455093.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-50e6acebc630849b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-7a1ae342d5ed75e6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-7ad7d505ba1f92b4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-02edba75232cb235.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-97d6ebae3e8eee4a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-e986adaa429163e8.js +0 -1
- /package/apps/pwa/out/_next/static/{T1mBQqmkwKDLGtyisDCOd → uC-N0emedeNOFuaBQFv-I}/_ssgManifest.js +0 -0
|
@@ -1,24 +1,53 @@
|
|
|
1
|
-
/** Fenced warm-Pod lease bridge
|
|
1
|
+
/** Fenced warm-Pod lease bridge. The Job remains authoritative. */
|
|
2
2
|
import { spawn, spawnSync } from 'node:child_process';
|
|
3
3
|
import { isAbsolute, join } from 'node:path';
|
|
4
|
-
import { mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
5
|
-
import { tmpdir } from 'node:os';
|
|
4
|
+
import { constants, closeSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { tmpdir, userInfo } from 'node:os';
|
|
6
|
+
import { dlopen, read } from 'bun:ffi';
|
|
7
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
6
8
|
import { fileURLToPath } from 'node:url';
|
|
7
9
|
import { debug } from '../../debug/log.js';
|
|
8
10
|
import { getUserConfig } from '../../user-config.js';
|
|
9
|
-
import { resolveWarmPoolProfile, resolveWarmPoolQuota, type WarmPoolTier } from '../../self-implement/pod-warm-pool-profile.js';
|
|
11
|
+
import { resolveWarmPoolProfile, resolveWarmPoolQuota, type WarmPoolProfile, type WarmPoolTier } from '../../self-implement/pod-warm-pool-profile.js';
|
|
10
12
|
import { WarmPodTransport, probeWarmPodContext, warmWorkerPodManifest, type WarmPodSpec, type WarmPodTransportResult } from './pod-warm-worker-pod.js';
|
|
11
13
|
import type { WarmWorkerCommand } from './pod-warm-worker-protocol.js';
|
|
12
14
|
import { patchWarmLeaseMetadata, warmLeaseMetadata } from './pod-warm-provenance.js';
|
|
15
|
+
import type { LogStore } from '../../mss/logging/log-store.js';
|
|
13
16
|
|
|
14
|
-
// Cold source install on node-b exceeded the
|
|
17
|
+
// Cold source install on node-b exceeded five minutes; recheck the pinned Pod UID in bounded intervals.
|
|
15
18
|
const POD_READY_WAIT_SECONDS = 900;
|
|
19
|
+
const POD_READY_RECHECK_SECONDS = 300;
|
|
20
|
+
const POD_READY_RETRY_DELAY_MS = 1_000;
|
|
21
|
+
|
|
22
|
+
function throwIfPrepareAborted(signal?: AbortSignal): void {
|
|
23
|
+
if (!signal?.aborted) return;
|
|
24
|
+
const reason = signal.reason;
|
|
25
|
+
throw reason instanceof Error ? reason : new Error(reason === undefined ? 'warm prepare cancelled' : String(reason));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function waitForPrepareRetry(signal?: AbortSignal): Promise<void> {
|
|
29
|
+
throwIfPrepareAborted(signal);
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const timer = setTimeout(() => {
|
|
32
|
+
signal?.removeEventListener('abort', abort);
|
|
33
|
+
resolve();
|
|
34
|
+
}, POD_READY_RETRY_DELAY_MS);
|
|
35
|
+
const abort = () => {
|
|
36
|
+
clearTimeout(timer);
|
|
37
|
+
signal?.removeEventListener('abort', abort);
|
|
38
|
+
try { throwIfPrepareAborted(signal); } catch (error) { reject(error); }
|
|
39
|
+
};
|
|
40
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
41
|
+
});
|
|
42
|
+
}
|
|
16
43
|
|
|
17
44
|
export type WarmReplayInput = Omit<WarmWorkerCommand, 'leaseId' | 'generation' | 'workerId' | 'key'>;
|
|
18
45
|
export interface WarmReplayResult { command: WarmWorkerCommand | null; transport: WarmPodTransportResult }
|
|
19
46
|
export interface WarmWorkerCandidate { host: string; tier: WarmPoolTier; spec: WarmPodSpec }
|
|
20
47
|
export interface WarmPodProviderOptions {
|
|
21
48
|
candidates: readonly WarmWorkerCandidate[];
|
|
49
|
+
/** One pinned template per host/tier. Each expands to the measured profile quota. */
|
|
50
|
+
poolTemplates?: readonly WarmWorkerCandidate[];
|
|
22
51
|
/** This address is resolved by the control-plane join/primary mechanism, never guessed from a Pod context. */
|
|
23
52
|
controlUrl: string;
|
|
24
53
|
memberToken: string;
|
|
@@ -31,6 +60,84 @@ export interface WarmPodProviderOptions {
|
|
|
31
60
|
maxShadowMs?: number;
|
|
32
61
|
now?: () => number;
|
|
33
62
|
kubectl?: (args: readonly string[], input?: string) => { status: number | null; stdout: string; stderr: string };
|
|
63
|
+
/** Private directory for cross-process prepare leases; defaults to a per-user directory under tmpdir(). */
|
|
64
|
+
prepareLockDirectory?: string;
|
|
65
|
+
/** Cancels a prepare waiting for another process to finish the same host/worker slot. */
|
|
66
|
+
prepareAbortSignal?: AbortSignal;
|
|
67
|
+
/** Gate file this pool serves, used to bind cold prepare legs to their 1-file gate measurement key. */
|
|
68
|
+
pairingGateFile?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface WarmPoolReconcileResult {
|
|
72
|
+
profile: string;
|
|
73
|
+
target: number;
|
|
74
|
+
ready: number;
|
|
75
|
+
failed: readonly string[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** One leg of a read-only warm/cold prepare-time measurement pair, bound to one 1-file gate key. */
|
|
79
|
+
export interface WarmPreparePairLeg {
|
|
80
|
+
file: string;
|
|
81
|
+
commit: string;
|
|
82
|
+
host: string;
|
|
83
|
+
/** True when an already-standing Pod was reused; false when this leg created the Pod. */
|
|
84
|
+
reuse: boolean;
|
|
85
|
+
prepareDurationMs: number;
|
|
86
|
+
totalDurationMs: number;
|
|
87
|
+
podUid: string;
|
|
88
|
+
success: boolean;
|
|
89
|
+
}
|
|
90
|
+
/** A read-only measurement row pairing the cold create and the warm reuse of the same 1-file gate. */
|
|
91
|
+
export interface WarmReuseTimePair {
|
|
92
|
+
gateKey: string;
|
|
93
|
+
file: string;
|
|
94
|
+
commit: string;
|
|
95
|
+
host: string;
|
|
96
|
+
cold: WarmPreparePairLeg | null;
|
|
97
|
+
warm: WarmPreparePairLeg | null;
|
|
98
|
+
}
|
|
99
|
+
/** The shared observation row each process emits (`pod.warm` / `warm-reuse-time-pair` in logs.db). */
|
|
100
|
+
export type WarmReuseTimeLegRow = WarmPreparePairLeg & { gateKey: string; leg: 'cold' | 'warm' };
|
|
101
|
+
const PAIR_CATEGORY = 'pod.warm';
|
|
102
|
+
const PAIR_EVENT = 'warm-reuse-time-pair';
|
|
103
|
+
|
|
104
|
+
function parseLegRow(value: unknown): WarmReuseTimeLegRow | null {
|
|
105
|
+
if (!value || typeof value !== 'object') return null;
|
|
106
|
+
const row = value as Record<string, unknown>;
|
|
107
|
+
const text = (key: string) => typeof row[key] === 'string';
|
|
108
|
+
const ms = (key: string) => typeof row[key] === 'number' && Number.isFinite(row[key] as number) && (row[key] as number) >= 0;
|
|
109
|
+
if (!(row.leg === 'cold' || row.leg === 'warm') || !text('gateKey') || !text('file') || !row.file
|
|
110
|
+
|| !text('commit') || !text('host') || !text('podUid') || typeof row.reuse !== 'boolean'
|
|
111
|
+
|| typeof row.success !== 'boolean' || !ms('prepareDurationMs') || !ms('totalDurationMs')) return null;
|
|
112
|
+
// The key must be the row's own file·commit·host; a mismatched key cannot join another gate.
|
|
113
|
+
if (row.gateKey !== `${row.file}/${row.commit}/${row.host}`) return null;
|
|
114
|
+
return row as unknown as WarmReuseTimeLegRow;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Pure read-only join: oldest→newest leg rows into one pair per 1-file gate key (newest leg wins). */
|
|
118
|
+
export function pairWarmReuseTimeLegs(rows: readonly unknown[]): WarmReuseTimePair[] {
|
|
119
|
+
const pairs = new Map<string, WarmReuseTimePair>();
|
|
120
|
+
for (const value of rows) {
|
|
121
|
+
const row = parseLegRow(value);
|
|
122
|
+
if (!row) continue;
|
|
123
|
+
const { gateKey, leg, ...measured } = row;
|
|
124
|
+
const pair = pairs.get(gateKey) ?? { gateKey, file: row.file, commit: row.commit, host: row.host, cold: null, warm: null };
|
|
125
|
+
pair[leg] = { file: measured.file, commit: measured.commit, host: measured.host, reuse: measured.reuse,
|
|
126
|
+
prepareDurationMs: measured.prepareDurationMs, totalDurationMs: measured.totalDurationMs,
|
|
127
|
+
podUid: measured.podUid, success: measured.success };
|
|
128
|
+
pairs.set(gateKey, pair);
|
|
129
|
+
}
|
|
130
|
+
return [...pairs.values()];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Read-only measurement rows from the logs.db both the cold CLI and the warm gate process write. */
|
|
134
|
+
export function readWarmReuseTimePairs(store: Pick<LogStore, 'queryAll'>, opts: { sinceMs?: number } = {}): WarmReuseTimePair[] {
|
|
135
|
+
const rows = store.queryAll({ exactCategories: [PAIR_CATEGORY], events: [PAIR_EVENT],
|
|
136
|
+
...(opts.sinceMs !== undefined ? { sinceMs: opts.sinceMs } : {}) });
|
|
137
|
+
// queryAll is newest-first; replay oldest-first so the newest leg per key wins.
|
|
138
|
+
return pairWarmReuseTimeLegs(rows.slice().reverse().map(row => {
|
|
139
|
+
try { return row.data ? JSON.parse(row.data) : null; } catch { return null; }
|
|
140
|
+
}));
|
|
34
141
|
}
|
|
35
142
|
|
|
36
143
|
const fallback = (reason: 'no-candidate' | 'lease-unavailable'): WarmReplayResult =>
|
|
@@ -42,19 +149,120 @@ const token = (value: string | undefined): value is string => typeof value === '
|
|
|
42
149
|
const workerId = (value: unknown): value is string => typeof value === 'string'
|
|
43
150
|
&& /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,95}$/.test(value);
|
|
44
151
|
|
|
152
|
+
function checkedPrivateDirectory(path: string): void {
|
|
153
|
+
try { mkdirSync(path, { mode: 0o700 }); } catch (error) {
|
|
154
|
+
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
|
|
155
|
+
}
|
|
156
|
+
const info = lstatSync(path);
|
|
157
|
+
if (!info.isDirectory() || info.isSymbolicLink() || (info.mode & 0o077) !== 0
|
|
158
|
+
|| (typeof process.getuid === 'function' && info.uid !== process.getuid()))
|
|
159
|
+
throw new Error('unsafe warm prepare lock directory');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const prepareLockLib = process.platform === 'linux' || process.platform === 'darwin'
|
|
163
|
+
? dlopen(process.platform === 'linux' ? 'libc.so.6' : 'libSystem.B.dylib', {
|
|
164
|
+
flock: { args: ['int', 'int'], returns: 'int' },
|
|
165
|
+
[process.platform === 'linux' ? '__errno_location' : '__error']: { args: [], returns: 'ptr' },
|
|
166
|
+
}) : null;
|
|
167
|
+
const prepareFlock = prepareLockLib?.symbols.flock ?? null;
|
|
168
|
+
const prepareErrno = prepareLockLib?.symbols[process.platform === 'linux' ? '__errno_location' : '__error'];
|
|
169
|
+
const LOCK_EX = 2;
|
|
170
|
+
const LOCK_NB = 4;
|
|
171
|
+
const LOCK_UN = 8;
|
|
172
|
+
const PREPARE_LOCK_WAIT_MS = (POD_READY_WAIT_SECONDS * 1_000) + 30_000;
|
|
173
|
+
|
|
174
|
+
async function withPrepareLease<T>(candidate: WarmWorkerCandidate, directory: string | undefined,
|
|
175
|
+
work: () => Promise<T>, signal?: AbortSignal): Promise<T | null> {
|
|
176
|
+
const uid = typeof process.getuid === 'function' ? process.getuid() : userInfo().uid;
|
|
177
|
+
const root = directory ?? join(tmpdir(), `elanous-warm-prepare-${uid}`);
|
|
178
|
+
const identity = createHash('sha256').update(`${candidate.host}/${candidate.spec.workerId}`).digest('hex');
|
|
179
|
+
let fd: number | undefined;
|
|
180
|
+
let acquired = false;
|
|
181
|
+
try {
|
|
182
|
+
checkedPrivateDirectory(root);
|
|
183
|
+
const lock = join(root, `${identity}.flock`);
|
|
184
|
+
fd = openSync(lock, constants.O_CREAT | constants.O_RDWR | (constants.O_NOFOLLOW ?? 0), 0o600);
|
|
185
|
+
const info = lstatSync(lock);
|
|
186
|
+
if (!info.isFile() || info.isSymbolicLink() || info.uid !== uid || (info.mode & 0o077) !== 0)
|
|
187
|
+
throw new Error('unsafe warm prepare lock file');
|
|
188
|
+
if (!prepareFlock) throw new Error('warm prepare OS locks are unavailable on this platform');
|
|
189
|
+
const lockDeadline = performance.now() + PREPARE_LOCK_WAIT_MS;
|
|
190
|
+
while (!acquired) {
|
|
191
|
+
if (signal?.aborted) throw new Error('warm prepare lock wait cancelled');
|
|
192
|
+
if (prepareFlock(fd, LOCK_EX | LOCK_NB) === 0) { acquired = true; break; }
|
|
193
|
+
const errno = prepareErrno ? read.i32(prepareErrno()) : -1;
|
|
194
|
+
// EWOULDBLOCK/EAGAIN: Linux 11, Darwin 35. EINTR is safe to retry.
|
|
195
|
+
if (errno !== 11 && errno !== 35 && errno !== 4)
|
|
196
|
+
throw new Error(`warm prepare flock failed (errno ${errno})`);
|
|
197
|
+
if (performance.now() >= lockDeadline) throw new Error('warm prepare lock wait timed out');
|
|
198
|
+
await new Promise<void>((resolve, reject) => {
|
|
199
|
+
const timer = setTimeout(done, Math.min(20, Math.max(1, lockDeadline - performance.now())));
|
|
200
|
+
const abort = () => { clearTimeout(timer); signal?.removeEventListener('abort', abort); reject(new Error('warm prepare lock wait cancelled')); };
|
|
201
|
+
function done() { signal?.removeEventListener('abort', abort); resolve(); }
|
|
202
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
return await work();
|
|
206
|
+
} catch (error) {
|
|
207
|
+
debug.log('pod.warm', 'prepare-lease-failed', { host: candidate.host,
|
|
208
|
+
workerId: candidate.spec.workerId, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' });
|
|
209
|
+
throw error;
|
|
210
|
+
} finally {
|
|
211
|
+
if (acquired && fd !== undefined) prepareFlock?.(fd, LOCK_UN);
|
|
212
|
+
if (fd !== undefined) closeSync(fd);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
45
216
|
export class WarmPodProvider {
|
|
46
217
|
private readonly fetcher: (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
47
218
|
private readonly now: () => number;
|
|
219
|
+
private readonly candidates: readonly WarmWorkerCandidate[];
|
|
220
|
+
private readonly profile: WarmPoolProfile;
|
|
221
|
+
/** This process's own legs, echoed on CLI stdout. Pairing never reads this — see readWarmReuseTimePairs. */
|
|
222
|
+
private readonly legs: WarmReuseTimeLegRow[] = [];
|
|
223
|
+
/** One terminal transition per fenced lease, including concurrent callers. Failed transitions remain retryable. */
|
|
224
|
+
private readonly terminalLeases = new Map<string, Promise<'sanitized' | 'quarantined'>>();
|
|
225
|
+
private readonly standingWorkers = new Map<string, Promise<boolean>>();
|
|
48
226
|
constructor(private readonly options: WarmPodProviderOptions) {
|
|
49
227
|
const url = new URL(options.controlUrl);
|
|
50
228
|
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.search || url.hash
|
|
51
229
|
|| url.pathname !== '/' || !token(options.memberToken)) throw new Error('invalid warm control address');
|
|
52
|
-
|
|
53
|
-
|
|
230
|
+
const profile = resolveWarmPoolProfile(options.profileConfig ?? getUserConfig());
|
|
231
|
+
this.profile = profile;
|
|
232
|
+
const templates = options.poolTemplates ?? [];
|
|
233
|
+
if (new Set(templates.map(candidate => `${candidate.host}/${candidate.tier}`)).size !== templates.length)
|
|
234
|
+
throw new Error('duplicate warm pool template');
|
|
235
|
+
const expanded = templates.flatMap(candidate => {
|
|
236
|
+
const quota = resolveWarmPoolQuota(profile, candidate.host, candidate.tier);
|
|
237
|
+
if (!quota || !candidate.spec.nodeName) throw new Error('unmeasured warm pool template');
|
|
238
|
+
return Array.from({ length: quota }, (_, index): WarmWorkerCandidate => ({
|
|
239
|
+
host: candidate.host, tier: candidate.tier, spec: {
|
|
240
|
+
...candidate.spec,
|
|
241
|
+
name: `warm-${candidate.host}-${candidate.tier}-${index + 1}`,
|
|
242
|
+
// The template worker ID is a generation token. Rotate it when a slot is quarantined.
|
|
243
|
+
workerId: `${candidate.spec.workerId}-${candidate.tier}-${index + 1}`,
|
|
244
|
+
},
|
|
245
|
+
}));
|
|
246
|
+
});
|
|
247
|
+
this.candidates = [...options.candidates, ...expanded];
|
|
248
|
+
if (this.candidates.some(candidate => !workerId(candidate.spec.workerId)
|
|
249
|
+
|| !candidate.host.trim() || resolveWarmPoolQuota(profile, candidate.host, candidate.tier) < 1
|
|
54
250
|
|| candidate.spec.memoryLimit !== (candidate.tier === 'lite' ? '2Gi' : '16Gi')
|
|
55
251
|
|| !warmWorkerPodManifest(candidate.spec))) throw new Error('invalid warm candidate');
|
|
56
|
-
if (new Set(
|
|
252
|
+
if (new Set(this.candidates.map(candidate => candidate.spec.workerId)).size !== this.candidates.length)
|
|
57
253
|
throw new Error('duplicate warm worker ID');
|
|
254
|
+
const names = new Set<string>();
|
|
255
|
+
const counts = new Map<string, number>();
|
|
256
|
+
for (const candidate of this.candidates) {
|
|
257
|
+
const identity = `${candidate.spec.kubectlContext}/${candidate.spec.namespace}/${candidate.spec.name}`;
|
|
258
|
+
if (names.has(identity)) throw new Error('duplicate warm Pod name');
|
|
259
|
+
names.add(identity);
|
|
260
|
+
const slot = `${candidate.host}/${candidate.tier}`;
|
|
261
|
+
const count = (counts.get(slot) ?? 0) + 1;
|
|
262
|
+
if (count > resolveWarmPoolQuota(profile, candidate.host, candidate.tier))
|
|
263
|
+
throw new Error('warm candidate quota exceeded');
|
|
264
|
+
counts.set(slot, count);
|
|
265
|
+
}
|
|
58
266
|
this.fetcher = options.fetch ?? fetch;
|
|
59
267
|
this.now = options.now ?? Date.now;
|
|
60
268
|
}
|
|
@@ -118,14 +326,61 @@ export class WarmPodProvider {
|
|
|
118
326
|
} finally { rmSync(directory, { recursive: true, force: true }); }
|
|
119
327
|
}
|
|
120
328
|
|
|
121
|
-
/**
|
|
329
|
+
/** Emit one measurement leg into the shared logs.db row the other process's leg joins. */
|
|
330
|
+
private recordPair(leg: 'cold' | 'warm', file: string, commit: string, host: string,
|
|
331
|
+
row: Omit<WarmPreparePairLeg, 'file' | 'commit' | 'host'>): void {
|
|
332
|
+
const legRow: WarmReuseTimeLegRow = { gateKey: `${file}/${commit}/${host}`, leg, file, commit, host, ...row };
|
|
333
|
+
this.legs.push(legRow);
|
|
334
|
+
debug.log(PAIR_CATEGORY, PAIR_EVENT, legRow, { level: 'info' });
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** The legs this process emitted (stdout echo only — pairing is readWarmReuseTimePairs over logs.db). */
|
|
338
|
+
warmReuseTimeLegs(): readonly WarmReuseTimeLegRow[] { return [...this.legs]; }
|
|
339
|
+
|
|
340
|
+
/** Prepare one named slot. A foreign or replaced Pod is never deleted. */
|
|
122
341
|
async ensureStandingWorker(candidate: WarmWorkerCandidate): Promise<boolean> {
|
|
123
|
-
const configured = this.
|
|
342
|
+
const configured = this.candidates.find(row => row.host === candidate.host
|
|
343
|
+
&& row.spec.workerId === candidate.spec.workerId);
|
|
344
|
+
if (!configured) return false;
|
|
345
|
+
const key = `${configured.host}/${configured.spec.workerId}`;
|
|
346
|
+
const inFlight = this.standingWorkers.get(key);
|
|
347
|
+
if (inFlight) return inFlight;
|
|
348
|
+
const preparation = withPrepareLease(configured, this.options.prepareLockDirectory,
|
|
349
|
+
() => this.prepareStandingWorker(configured, this.options.prepareAbortSignal), this.options.prepareAbortSignal)
|
|
350
|
+
.then(result => result === true);
|
|
351
|
+
this.standingWorkers.set(key, preparation);
|
|
352
|
+
try { return await preparation; }
|
|
353
|
+
finally { if (this.standingWorkers.get(key) === preparation) this.standingWorkers.delete(key); }
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
private async prepareStandingWorker(candidate: WarmWorkerCandidate, signal?: AbortSignal): Promise<boolean> {
|
|
357
|
+
throwIfPrepareAborted(signal);
|
|
358
|
+
const startedAt = this.now();
|
|
359
|
+
const outcome: { preexisting: boolean; podUid: string | null } = { preexisting: false, podUid: null };
|
|
360
|
+
const ok = await this.coldPrepare(candidate, outcome, signal);
|
|
361
|
+
const totalDurationMs = this.now() - startedAt;
|
|
362
|
+
const gateFile = this.options.pairingGateFile;
|
|
363
|
+
// An unset gate file skips the measurement row entirely instead of recording an unpaired one.
|
|
364
|
+
if (gateFile) this.recordPair('cold', gateFile, candidate.spec.cutSha, candidate.host,
|
|
365
|
+
{ reuse: outcome.preexisting, prepareDurationMs: outcome.preexisting ? 0 : totalDurationMs,
|
|
366
|
+
totalDurationMs, podUid: outcome.podUid ?? '', success: ok });
|
|
367
|
+
return ok;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
private async coldPrepare(candidate: WarmWorkerCandidate,
|
|
371
|
+
outcome: { preexisting: boolean; podUid: string | null }, signal?: AbortSignal): Promise<boolean> {
|
|
372
|
+
const configured = this.candidates.find(row => row.host === candidate.host
|
|
124
373
|
&& row.spec.workerId === candidate.spec.workerId && row.spec.cutSha === candidate.spec.cutSha
|
|
125
374
|
&& keyEquals(row.spec.key, candidate.spec.key));
|
|
126
|
-
//
|
|
127
|
-
|
|
375
|
+
// Legacy slot warm-HOST and numbered profile slots can coexist during migration.
|
|
376
|
+
const prefix = configured ? `warm-${configured.host}` : '';
|
|
377
|
+
const suffix = configured?.spec.name.startsWith(`${prefix}-`)
|
|
378
|
+
? configured.spec.name.slice(prefix.length + 1) : '';
|
|
379
|
+
if (!configured || !configured.spec.nodeName
|
|
380
|
+
|| (configured.spec.name !== prefix && !/^[1-9][0-9]*$/.test(suffix)
|
|
381
|
+
&& !/^(lite|standard)-[1-9][0-9]*$/.test(suffix))
|
|
128
382
|
|| candidate.spec.name !== configured.spec.name || candidate.spec.nodeName !== configured.spec.nodeName) return false;
|
|
383
|
+
throwIfPrepareAborted(signal);
|
|
129
384
|
const { spec, host } = configured;
|
|
130
385
|
const nodeName = spec.nodeName;
|
|
131
386
|
if (!nodeName) return false;
|
|
@@ -206,7 +461,19 @@ export class WarmPodProvider {
|
|
|
206
461
|
if (hostPods.length > 1) return false;
|
|
207
462
|
let pod = hostPods[0];
|
|
208
463
|
const preexisting = !!pod;
|
|
464
|
+
outcome.preexisting = preexisting;
|
|
209
465
|
if (pod && !matches(pod)) return false;
|
|
466
|
+
// Old generations still occupy memory even when their ledger rows are quarantined.
|
|
467
|
+
// Count physical warm Pods on the pinned node before admitting a new one.
|
|
468
|
+
const physical = listed.items.filter((row: any) => row?.metadata?.labels?.['elanous.kind'] === 'warm-worker'
|
|
469
|
+
&& row?.spec?.nodeName === nodeName && !row?.metadata?.deletionTimestamp
|
|
470
|
+
&& row?.status?.phase !== 'Succeeded' && row?.status?.phase !== 'Failed'
|
|
471
|
+
&& row?.spec?.containers?.[0]?.resources?.limits?.memory === spec.memoryLimit).length;
|
|
472
|
+
if (!preexisting && physical >= resolveWarmPoolQuota(this.profile, host, configured.tier)) {
|
|
473
|
+
debug.log('pod.warm', 'pool-slot-refused', { host, tier: configured.tier,
|
|
474
|
+
workerId: spec.workerId, reason: 'physical-quota', physical }, { level: 'warn' });
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
210
477
|
const readLedger = async (): Promise<any[] | null> => {
|
|
211
478
|
try {
|
|
212
479
|
const query = new URLSearchParams({ kind: 'pod-worker-lease', machine: host, name: spec.workerId });
|
|
@@ -218,15 +485,31 @@ export class WarmPodProvider {
|
|
|
218
485
|
if (!pod) {
|
|
219
486
|
// A worker ID already bound to another UID must not leave a newly created, unregistered Pod.
|
|
220
487
|
const before = await readLedger();
|
|
488
|
+
throwIfPrepareAborted(signal);
|
|
221
489
|
if (!before || before.length !== 0) return false;
|
|
222
490
|
pod = parse(kubectl(['create', '-f', '-', '-o', 'json'], JSON.stringify(manifest)));
|
|
223
491
|
if (!matches(pod)) return false;
|
|
224
492
|
}
|
|
225
493
|
const uid = pod.metadata.uid as string;
|
|
226
|
-
|
|
227
|
-
const ready =
|
|
228
|
-
|
|
229
|
-
|
|
494
|
+
outcome.podUid = uid;
|
|
495
|
+
const ready = (value: any) => matches(value) && value.metadata.uid === uid
|
|
496
|
+
&& value.status?.phase === 'Running'
|
|
497
|
+
&& value.status?.conditions?.some((row: any) => row.type === 'Ready' && row.status === 'True');
|
|
498
|
+
const deadline = this.now() + POD_READY_WAIT_SECONDS * 1000;
|
|
499
|
+
let isReady = false;
|
|
500
|
+
while (this.now() < deadline) {
|
|
501
|
+
const remainingSeconds = Math.ceil((deadline - this.now()) / 1000);
|
|
502
|
+
const waitSeconds = Math.min(POD_READY_RECHECK_SECONDS, remainingSeconds);
|
|
503
|
+
throwIfPrepareAborted(signal);
|
|
504
|
+
kubectl(['wait', `pod/${spec.name}`, '--for=condition=Ready', `--timeout=${waitSeconds}s`]);
|
|
505
|
+
throwIfPrepareAborted(signal);
|
|
506
|
+
const observed = parse(kubectl(['get', `pod/${spec.name}`, '-o', 'json']));
|
|
507
|
+
throwIfPrepareAborted(signal);
|
|
508
|
+
if (ready(observed)) { isReady = true; break; }
|
|
509
|
+
if (!matches(observed) || observed.metadata.uid !== uid) return false;
|
|
510
|
+
if (this.now() < deadline) await waitForPrepareRetry(signal);
|
|
511
|
+
}
|
|
512
|
+
if (!isReady) return false;
|
|
230
513
|
// Re-read after create: a registration racing the preflight must fail before our POST.
|
|
231
514
|
const rows = await readLedger();
|
|
232
515
|
if (!rows) return false;
|
|
@@ -244,6 +527,7 @@ export class WarmPodProvider {
|
|
|
244
527
|
// An unregistered Pod could have been created by anyone with namespace access.
|
|
245
528
|
// Only this invocation's successful create response may become a new ledger row.
|
|
246
529
|
if (preexisting) return false;
|
|
530
|
+
throwIfPrepareAborted(signal);
|
|
247
531
|
let registered: { status: number; value: any };
|
|
248
532
|
try {
|
|
249
533
|
registered = await this.control('/v1/workers/pod', credential, {
|
|
@@ -258,27 +542,60 @@ export class WarmPodProvider {
|
|
|
258
542
|
}
|
|
259
543
|
|
|
260
544
|
async prepareWorker(workerId: string): Promise<boolean> {
|
|
261
|
-
const candidate = this.
|
|
545
|
+
const candidate = this.candidates.find(row => row.spec.workerId === workerId);
|
|
262
546
|
if (!candidate) return false;
|
|
263
547
|
return this.ensureStandingWorker(candidate);
|
|
264
548
|
}
|
|
265
549
|
|
|
550
|
+
/** One bounded reconciliation pass. Failed slots stay unavailable until a fresh worker ID is configured. */
|
|
551
|
+
async reconcilePool(): Promise<WarmPoolReconcileResult> {
|
|
552
|
+
const failed: string[] = [];
|
|
553
|
+
let ready = 0;
|
|
554
|
+
for (const candidate of this.candidates) {
|
|
555
|
+
let prepared = false;
|
|
556
|
+
try { prepared = await this.ensureStandingWorker(candidate); }
|
|
557
|
+
catch { /* Uncertain state is never counted as ready. */ }
|
|
558
|
+
if (prepared) ready++;
|
|
559
|
+
else failed.push(candidate.spec.workerId);
|
|
560
|
+
debug.log('pod.warm', 'pool-slot', { profile: this.profile,
|
|
561
|
+
host: candidate.host, tier: candidate.tier, workerId: candidate.spec.workerId,
|
|
562
|
+
pod: candidate.spec.name, ready: prepared }, { level: prepared ? 'info' : 'warn' });
|
|
563
|
+
}
|
|
564
|
+
const result = { profile: this.profile,
|
|
565
|
+
target: this.candidates.length, ready, failed };
|
|
566
|
+
debug.log('pod.warm', 'pool-reconcile', result, { level: failed.length ? 'warn' : 'info' });
|
|
567
|
+
return result;
|
|
568
|
+
}
|
|
569
|
+
|
|
266
570
|
/** Acquire a matching idle worker before exec. Every uncertain result is fenced out of reuse. */
|
|
267
571
|
async request(input: WarmReplayInput): Promise<WarmReplayResult> {
|
|
572
|
+
const requestedAt = this.now();
|
|
268
573
|
if (input.protocol !== 1 || input.envProfile !== 'gate-v1' || !Array.isArray(input.files)
|
|
269
574
|
|| !input.files.length || !Number.isFinite(input.deadlineAt) || input.deadlineAt <= this.now()) return fallback('no-candidate');
|
|
270
|
-
const
|
|
271
|
-
if (!
|
|
575
|
+
const matches = this.candidates.filter(row => row.spec.cutSha === input.cutSha);
|
|
576
|
+
if (!matches.length) return fallback('no-candidate');
|
|
577
|
+
let lastFallback: WarmReplayResult = fallback('lease-unavailable');
|
|
578
|
+
// The control plane chooses an idle worker within a host/key. Probe each distinct
|
|
579
|
+
// host/key once so a busy first host cannot strand an idle second pool.
|
|
580
|
+
const tried = new Set<string>();
|
|
581
|
+
for (const candidate of matches) {
|
|
272
582
|
const { spec, host } = candidate;
|
|
583
|
+
const poolKey = `${host}/${spec.key.context}/${spec.key.imageDigest}/${spec.key.lockHash}/${spec.key.architecture}`;
|
|
584
|
+
if (tried.has(poolKey)) continue;
|
|
585
|
+
tried.add(poolKey);
|
|
273
586
|
const contextFailure = probeWarmPodContext(spec.kubectlContext, this.options.kubectl);
|
|
274
|
-
if (contextFailure)
|
|
587
|
+
if (contextFailure) { lastFallback = { command: null, transport: contextFailure }; continue; }
|
|
275
588
|
let acquired: { status: number; value: any };
|
|
276
589
|
try {
|
|
277
590
|
acquired = await this.control('/v1/leases/pod-worker', this.options.memberToken, {
|
|
278
591
|
machine: host, key: spec.key, runId: input.runId, attemptId: input.attemptId,
|
|
279
592
|
ttlMs: this.options.leaseTtlMs ?? 30 * 60_000,
|
|
280
593
|
});
|
|
281
|
-
} catch {
|
|
594
|
+
} catch { lastFallback = fallback('lease-unavailable'); continue; }
|
|
595
|
+
if (acquired.status === 409 || acquired.status === 503) {
|
|
596
|
+
lastFallback = fallback('lease-unavailable');
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
282
599
|
if (acquired.status !== 200) return fallback('lease-unavailable');
|
|
283
600
|
const row = acquired.value;
|
|
284
601
|
const leasedId = row?.attrs?.leaseId;
|
|
@@ -289,7 +606,7 @@ export class WarmPodProvider {
|
|
|
289
606
|
|| !Number.isSafeInteger(generation) || generation < 1) return fallback('lease-unavailable');
|
|
290
607
|
// Acquisition selects by host/key, not by worker ID. Resolve the returned worker
|
|
291
608
|
// before choosing its transport; an unknown worker must not be executed or fenced.
|
|
292
|
-
const leasedCandidate = this.
|
|
609
|
+
const leasedCandidate = this.candidates.find(entry => entry.host === host
|
|
293
610
|
&& entry.spec.workerId === row.name && entry.spec.cutSha === input.cutSha
|
|
294
611
|
&& keyEquals(entry.spec.key, spec.key));
|
|
295
612
|
if (!leasedCandidate) {
|
|
@@ -305,6 +622,7 @@ export class WarmPodProvider {
|
|
|
305
622
|
return fallback('lease-unavailable');
|
|
306
623
|
}
|
|
307
624
|
const command: WarmWorkerCommand = { ...input, leaseId: leasedId, generation, podUid,
|
|
625
|
+
podName: leasedCandidate.spec.name, podContext: leasedCandidate.spec.kubectlContext, image: leasedCandidate.spec.image,
|
|
308
626
|
requestedAt: new Date(this.now()).toISOString(),
|
|
309
627
|
workerId: row.name, key: leasedCandidate.spec.key };
|
|
310
628
|
const admitted = patchWarmLeaseMetadata(leasedCandidate.spec, command, 'leased', this.options.kubectl);
|
|
@@ -320,6 +638,16 @@ export class WarmPodProvider {
|
|
|
320
638
|
let transport: WarmPodTransportResult;
|
|
321
639
|
try { transport = await this.runTransport(leasedCandidate.spec, command); }
|
|
322
640
|
catch { transport = { status: 'fallback', reason: 'exec-failed', reply: null }; }
|
|
641
|
+
// Warm leg: the lease reused an already-standing Pod, so no prepare interval elapsed.
|
|
642
|
+
// Only a 1-file gate has a pairing key; a multi-file gate is skipped (with its reason), never keyed by files[0].
|
|
643
|
+
const gateFile = command.files.length === 1 ? command.files[0] : undefined;
|
|
644
|
+
if (command.files.length !== 1) debug.log(PAIR_CATEGORY, `${PAIR_EVENT}-skipped`, { reason: 'not-single-file-gate',
|
|
645
|
+
fileCount: command.files.length, commit: command.cutSha, host }, { level: 'info' });
|
|
646
|
+
if (gateFile) this.recordPair('warm', gateFile, command.cutSha, host, {
|
|
647
|
+
reuse: true, prepareDurationMs: 0,
|
|
648
|
+
totalDurationMs: this.now() - requestedAt, podUid,
|
|
649
|
+
success: transport.status === 'observed' && transport.reason === 'complete',
|
|
650
|
+
});
|
|
323
651
|
if (transport.status !== 'observed' || transport.reason !== 'complete') {
|
|
324
652
|
await this.quarantine(command, transport.reason);
|
|
325
653
|
return { command, transport };
|
|
@@ -327,6 +655,8 @@ export class WarmPodProvider {
|
|
|
327
655
|
// Keep the lease fenced until the gate has compared these bytes with its Job baseline.
|
|
328
656
|
// A missing finalization leaves the worker leased and therefore unavailable.
|
|
329
657
|
return { command, transport };
|
|
658
|
+
}
|
|
659
|
+
return lastFallback;
|
|
330
660
|
}
|
|
331
661
|
|
|
332
662
|
private async transition(command: WarmWorkerCommand, action: 'release' | 'sanitize' | 'quarantine',
|
|
@@ -340,7 +670,25 @@ export class WarmPodProvider {
|
|
|
340
670
|
}
|
|
341
671
|
|
|
342
672
|
async finalize(command: WarmWorkerCommand): Promise<void> {
|
|
343
|
-
const
|
|
673
|
+
const key = JSON.stringify([command.workerId, command.leaseId, command.generation, command.podUid]);
|
|
674
|
+
const prior = this.terminalLeases.get(key);
|
|
675
|
+
if (prior) {
|
|
676
|
+
const outcome = await prior;
|
|
677
|
+
debug.log('pod.provenance', 'lease-terminal-suppressed', { workerId: command.workerId,
|
|
678
|
+
leaseId: command.leaseId, generation: command.generation, requested: 'finalize', outcome });
|
|
679
|
+
if (outcome === 'quarantined') throw new Error('warm lease already quarantined');
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
const terminal = this.finalizeOnce(command);
|
|
683
|
+
this.terminalLeases.set(key, terminal);
|
|
684
|
+
let outcome: 'sanitized' | 'quarantined';
|
|
685
|
+
try { outcome = await terminal; }
|
|
686
|
+
catch (error) { if (this.terminalLeases.get(key) === terminal) this.terminalLeases.delete(key); throw error; }
|
|
687
|
+
if (outcome === 'quarantined') throw new Error('warm sanitize unavailable');
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
private async finalizeOnce(command: WarmWorkerCommand): Promise<'sanitized' | 'quarantined'> {
|
|
691
|
+
const candidate = this.candidates.find(row => row.spec.workerId === command.workerId);
|
|
344
692
|
if (!candidate) throw new Error('warm finalize candidate missing');
|
|
345
693
|
const released = await this.transition(command, 'release');
|
|
346
694
|
const machineToken = this.options.machineTokens?.[candidate.host];
|
|
@@ -350,20 +698,36 @@ export class WarmPodProvider {
|
|
|
350
698
|
if (!cleared.ok) debug.log('pod.provenance', 'lease-patch-refused', { pod: candidate.spec.name,
|
|
351
699
|
podUid: command.podUid, leaseId: command.leaseId, reason: cleared.reason }, { level: 'warn' });
|
|
352
700
|
if (!cleared.ok || !token(machineToken) || !await this.transition(command, 'sanitize', machineToken)) {
|
|
353
|
-
await this.
|
|
354
|
-
|
|
701
|
+
await this.quarantineOnce(command, 'sanitize-unavailable', true);
|
|
702
|
+
return 'quarantined';
|
|
355
703
|
}
|
|
356
704
|
debug.log('pod.provenance', 'lease-end', { pod: candidate.spec.name, podUid: command.podUid,
|
|
357
705
|
leaseId: command.leaseId, runId: command.runId, attemptId: command.attemptId,
|
|
358
706
|
releasedAt: new Date(this.now()).toISOString(), result: 'sanitized' });
|
|
707
|
+
return 'sanitized';
|
|
359
708
|
}
|
|
360
709
|
|
|
361
710
|
/** `leaseStarted=false` marks a refusal before `lease-start`, so start/end history stays paired. */
|
|
362
711
|
async quarantine(command: WarmWorkerCommand, reason: string, leaseStarted = true): Promise<void> {
|
|
712
|
+
const key = JSON.stringify([command.workerId, command.leaseId, command.generation, command.podUid]);
|
|
713
|
+
const prior = this.terminalLeases.get(key);
|
|
714
|
+
if (prior) {
|
|
715
|
+
const outcome = await prior;
|
|
716
|
+
debug.log('pod.provenance', 'lease-terminal-suppressed', { workerId: command.workerId,
|
|
717
|
+
leaseId: command.leaseId, generation: command.generation, requested: 'quarantine', outcome });
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
const terminal = this.quarantineOnce(command, reason, leaseStarted).then(() => 'quarantined' as const);
|
|
721
|
+
this.terminalLeases.set(key, terminal);
|
|
722
|
+
try { await terminal; }
|
|
723
|
+
catch (error) { if (this.terminalLeases.get(key) === terminal) this.terminalLeases.delete(key); throw error; }
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
private async quarantineOnce(command: WarmWorkerCommand, reason: string, leaseStarted: boolean): Promise<void> {
|
|
363
727
|
if (!await this.transition(command, 'quarantine', this.options.memberToken, reason.slice(0, 120) || 'mismatch')) {
|
|
364
728
|
throw new Error('warm quarantine unavailable');
|
|
365
729
|
}
|
|
366
|
-
const candidate = this.
|
|
730
|
+
const candidate = this.candidates.find(row => row.spec.workerId === command.workerId);
|
|
367
731
|
if (candidate) {
|
|
368
732
|
const cleared = patchWarmLeaseMetadata(candidate.spec, command, 'quarantined', this.options.kubectl);
|
|
369
733
|
if (!cleared.ok && reason !== 'provenance-unavailable') debug.log('pod.provenance', 'lease-patch-refused', { pod: candidate.spec.name,
|
|
@@ -381,11 +745,17 @@ let configuredProvider: WarmPodProvider | null = null;
|
|
|
381
745
|
/** The trusted producer configures this once; an absent provider fails closed without creating a Pod. */
|
|
382
746
|
export function configureWarmPodProvider(provider: WarmPodProvider | null): void { configuredProvider = provider; }
|
|
383
747
|
/** Optional private producer config; a missing/unsafe file leaves the Job path untouched. */
|
|
384
|
-
export function loadWarmPodProvider(path: string): WarmPodProvider {
|
|
748
|
+
export function loadWarmPodProvider(path: string, pairingGateFile?: string): WarmPodProvider {
|
|
385
749
|
if (!isAbsolute(path) || (statSync(path).mode & 0o077) !== 0) throw new Error('warm provider config must be private');
|
|
386
750
|
const parsed: unknown = JSON.parse(readFileSync(path, 'utf8'));
|
|
387
751
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('invalid warm provider config');
|
|
388
|
-
|
|
752
|
+
const options = parsed as WarmPodProviderOptions;
|
|
753
|
+
// The production entry points (--prepare/--reconcile/--serve) bind the 1-file gate key here.
|
|
754
|
+
if (pairingGateFile !== undefined) {
|
|
755
|
+
if (!pairingGateFile.trim()) throw new Error('warm provider pairing gate file must not be empty');
|
|
756
|
+
options.pairingGateFile = pairingGateFile;
|
|
757
|
+
}
|
|
758
|
+
return new WarmPodProvider(options);
|
|
389
759
|
}
|
|
390
760
|
function currentProvider(): WarmPodProvider | null {
|
|
391
761
|
if (configuredProvider) return configuredProvider;
|
|
@@ -396,7 +766,13 @@ function currentProvider(): WarmPodProvider | null {
|
|
|
396
766
|
return configuredProvider;
|
|
397
767
|
}
|
|
398
768
|
export async function requestWarmPodReplay(input: WarmReplayInput): Promise<WarmReplayResult> {
|
|
399
|
-
|
|
769
|
+
const provider = currentProvider();
|
|
770
|
+
const result = provider ? await provider.request(input) : fallback('no-candidate');
|
|
771
|
+
debug.log('pod.warm', 'request-selection', { runId: input.runId, attemptId: input.attemptId,
|
|
772
|
+
cutSha: input.cutSha, selected: result.command !== null,
|
|
773
|
+
workerId: result.command?.workerId ?? null, status: result.transport.status,
|
|
774
|
+
reason: result.transport.reason });
|
|
775
|
+
return result;
|
|
400
776
|
}
|
|
401
777
|
export async function quarantineWarmPodReplay(command: WarmWorkerCommand, reason: string): Promise<void> {
|
|
402
778
|
const provider = currentProvider();
|
|
@@ -409,13 +785,86 @@ export async function finalizeWarmPodReplay(command: WarmWorkerCommand): Promise
|
|
|
409
785
|
await provider.finalize(command);
|
|
410
786
|
}
|
|
411
787
|
|
|
788
|
+
|
|
789
|
+
/** CLI 인자 — `--prepare <workerId> [gateFile]` · `--reconcile [gateFile]` · `--serve [gateFile]` · `--pairs`.
|
|
790
|
+
* gateFile 은 --prepare 에서만 셋째 칸, 나머지는 둘째 칸이다. `--pairs` 는 logs.db 읽기 전용(인자 없음). */
|
|
791
|
+
export function parseWarmPodProviderArgs(argv: readonly string[]): { action: '--prepare' | '--reconcile' | '--serve' | '--pairs'; workerId?: string; gateFile?: string } {
|
|
792
|
+
const usage = () => new Error('usage: --prepare <workerId> [gateFile] | --reconcile [gateFile] | --serve [gateFile]'
|
|
793
|
+
+ ' with private provider config | --pairs');
|
|
794
|
+
if (argv[0] === '--pairs') {
|
|
795
|
+
if (argv.length !== 1) throw usage();
|
|
796
|
+
return { action: '--pairs' };
|
|
797
|
+
}
|
|
798
|
+
const [action, ...rest] = argv;
|
|
799
|
+
if (action === '--prepare') {
|
|
800
|
+
const [workerId, gateFile, extra] = rest;
|
|
801
|
+
if (!workerId || extra !== undefined || (gateFile !== undefined && !gateFile.trim())) throw usage();
|
|
802
|
+
return { action, workerId, ...(gateFile !== undefined ? { gateFile } : {}) };
|
|
803
|
+
}
|
|
804
|
+
if (action === '--reconcile' || action === '--serve') {
|
|
805
|
+
const [gateFile, extra] = rest;
|
|
806
|
+
if (extra !== undefined || (gateFile !== undefined && !gateFile.trim())) throw usage();
|
|
807
|
+
return { action, ...(gateFile !== undefined ? { gateFile } : {}) };
|
|
808
|
+
}
|
|
809
|
+
throw usage();
|
|
810
|
+
}
|
|
811
|
+
|
|
412
812
|
if (import.meta.main) {
|
|
413
|
-
const
|
|
813
|
+
const { action, workerId, gateFile } = parseWarmPodProviderArgs(process.argv.slice(2));
|
|
414
814
|
const file = process.env.ELANOUS_WARM_POD_PROVIDER_CONFIG;
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
815
|
+
// The 1-file gate key this pool serves, bound into every cold prepare leg of this process.
|
|
816
|
+
const load = () => (file ? loadWarmPodProvider(file, gateFile) : null);
|
|
817
|
+
if (action === '--pairs') {
|
|
818
|
+
// Read-only: join the cold CLI legs and the warm gate legs both processes wrote to logs.db.
|
|
819
|
+
try {
|
|
820
|
+
const storeMod = await import('../../mss/logging/log-store.js');
|
|
821
|
+
const store = storeMod.LogStore.openReadOnly(storeMod.logsDbPath());
|
|
822
|
+
try { process.stdout.write(JSON.stringify({ warmReuseTimePairs: readWarmReuseTimePairs(store) }) + '\n'); }
|
|
823
|
+
finally { store.close(); }
|
|
824
|
+
} catch {
|
|
825
|
+
process.stdout.write(JSON.stringify({ warmReuseTimePairs: null, reason: 'logs-unavailable' }) + '\n');
|
|
826
|
+
process.exitCode = 2;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
// The cold legs must reach the same logs.db the warm gate process (gate-node) writes; fail-open.
|
|
830
|
+
if (action !== '--pairs') try { await (await import('../../domains/standalone-log-sink.js')).registerStandaloneLogSink('pod-warm'); }
|
|
831
|
+
catch { /* the file trail stays the source of truth */ }
|
|
832
|
+
// This process's own legs, echoed for the caller; the pair itself is read with --pairs.
|
|
833
|
+
const emitPairs = (provider: WarmPodProvider) => {
|
|
834
|
+
const legs = provider.warmReuseTimeLegs();
|
|
835
|
+
if (legs.length) process.stdout.write(JSON.stringify({ warmReuseTimeLegs: legs }) + '\n');
|
|
836
|
+
};
|
|
837
|
+
if (action === '--serve') {
|
|
838
|
+
// Reloading the private config lets the owner rotate a quarantined slot to a new worker ID.
|
|
839
|
+
// The old ID remains fenced in the ledger; this loop never deletes a Pod or rewrites the ledger.
|
|
840
|
+
while (true) {
|
|
841
|
+
try {
|
|
842
|
+
const provider = load();
|
|
843
|
+
const result = provider ? await provider.reconcilePool() : null;
|
|
844
|
+
process.stdout.write(JSON.stringify(result ?? { target: 0, ready: 0, failed: ['config-unavailable'] }) + '\n');
|
|
845
|
+
if (provider) emitPairs(provider);
|
|
846
|
+
} catch {
|
|
847
|
+
debug.log('pod.warm', 'pool-reconcile-unavailable', { reason: 'config-unavailable' }, { level: 'warn' });
|
|
848
|
+
process.stdout.write(JSON.stringify({ target: 0, ready: 0, failed: ['config-unavailable'] }) + '\n');
|
|
849
|
+
}
|
|
850
|
+
await Bun.sleep(60_000);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
if (action === '--reconcile') {
|
|
854
|
+
let result: WarmPoolReconcileResult | null = null;
|
|
855
|
+
let provider: WarmPodProvider | null = null;
|
|
856
|
+
try { provider = load(); result = provider ? await provider.reconcilePool() : null; }
|
|
857
|
+
catch { /* An invalid private config is a failed pass, never a Pod mutation. */ }
|
|
858
|
+
process.stdout.write(JSON.stringify(result ?? { target: 0, ready: 0, failed: ['config-unavailable'] }) + '\n');
|
|
859
|
+
if (provider) emitPairs(provider);
|
|
860
|
+
if (!result || result.ready !== result.target) process.exitCode = 2;
|
|
861
|
+
} else if (action === '--prepare') {
|
|
862
|
+
let prepared = false;
|
|
863
|
+
let provider: WarmPodProvider | null = null;
|
|
864
|
+
try { provider = load(); prepared = provider ? await provider.prepareWorker(workerId!) : false; }
|
|
865
|
+
catch { /* An invalid private config is a failed preparation, never a Pod mutation. */ }
|
|
866
|
+
process.stdout.write(JSON.stringify({ workerId, prepared }) + '\n');
|
|
867
|
+
if (provider) emitPairs(provider);
|
|
868
|
+
if (!prepared) process.exitCode = 2;
|
|
869
|
+
}
|
|
421
870
|
}
|