elanous 0.2.35 → 0.2.37
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/{T1mBQqmkwKDLGtyisDCOd → 8kITumG9GAZ1LwT9POrAR}/_buildManifest.js +1 -1
- package/apps/pwa/out/_next/static/chunks/1041-5841aae848fb6403.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1087.3177e54f672dc526.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1105.efe1e13503ac7a33.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1224.2ceca6c180d5130b.js +62 -0
- package/apps/pwa/out/_next/static/chunks/{9952.5daf5f94063a2b3e.js → 1262.a89bb27a5b2f844e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1417-1768233ac92f8a5d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1612.81bbc19af06af16c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1746.3563617ea64023a3.js +115 -0
- package/apps/pwa/out/_next/static/chunks/1766.728d42053070bb2d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1773.d38e945481e04125.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1974-c12f9600da3d3c26.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1dc5a567.9b092bfae3d7099b.js → 1ed5e8af.cf424815c7c0efad.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{4705-194e64e47ca0fbdb.js → 2002-a43a3f380df873ec.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2061.7232dc9dc058417f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{13.02a07436dec15f89.js → 210.90b2cbd64926cf34.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{9512.5a76207a730e0491.js → 2283.edd9627b7bdfb546.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/{6866-79aa5b4e47cac6aa.js → 2318-450e75d07605b8fe.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{938.66ad1ef0e5abae1c.js → 2375.bf364c85dbb6f085.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/244-a810d282a20f1783.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2479-fe7e188bd9402d14.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2514.03b60f266531a015.js +148 -0
- package/apps/pwa/out/_next/static/chunks/2526-a657538869888604.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2533-6f0341ff075809e9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2676.e7994c695ef0f30d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2734.3ffdf54254fb6f99.js +27 -0
- package/apps/pwa/out/_next/static/chunks/2770-23252bf3a37880b9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2969.66b8b545139d1544.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3128.a9210d08800ea6a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3199.6a1a9b0e6cd01dc6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3212.12179fd3eee8cef1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9214.63a510d39178e997.js → 3578.4e9f9948a9a6f2f4.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/3760-2909ddb9279281cd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3825.1921b5901bf33dce.js +36 -0
- package/apps/pwa/out/_next/static/chunks/3994-63eb884ed0547011.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4036.d54e55e1ebf5acdc.js +90 -0
- package/apps/pwa/out/_next/static/chunks/4115.5b438b99f0ae729f.js +226 -0
- package/apps/pwa/out/_next/static/chunks/4269.d8f9f34bb0fc6c43.js +287 -0
- package/apps/pwa/out/_next/static/chunks/431-4eed5bd2c5fe1719.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2018.a741297bce759490.js → 4313.76f232c304a15bde.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{1165.bad13fc9151a7288.js → 4324.c99ba3e83eb1b691.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{3f7c4ee5.3dde54f01a816613.js → 4337e36d.f42c3b391b3d8f20.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{7385.50a308a477bdfc21.js → 4383.18e5c54db3a22c88.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4599-96eff3f2a9c242c1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8557-e7898769499cd56f.js → 4725-34bed25720cc326b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4825-a9d9d4e9f5892309.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5483-dd5d7697f6bdb106.js → 4833-7934c592c5569f00.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4879-68518f547a207aff.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4896.f9b70285363b27b2.js +24 -0
- package/apps/pwa/out/_next/static/chunks/{8444.4c26b379ca2b9596.js → 4931.95cc6dbd230774ca.js} +17 -17
- package/apps/pwa/out/_next/static/chunks/4937-28d437999b77c9dd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/49aa54ab.80f39398e0ed0f30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3408.21d1570602f4b6c0.js → 5046.7fd5cbd859299448.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5056.482687319b82638e.js +104 -0
- package/apps/pwa/out/_next/static/chunks/506.16b83d56a437a9ee.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5260-20593b0a25d0afbc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5404.cc4e9fec26fd5925.js +1 -0
- package/apps/pwa/out/_next/static/chunks/576.485e83e3f3dec19a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5832-a6be14e663f65726.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5854-56648a9fe646b9a0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/58edce7d.f1b0c72acf13b123.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5925.3b1f8b135710249b.js +93 -0
- package/apps/pwa/out/_next/static/chunks/5952.294fc574a8625184.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{b6705a4f-b9a9047de9eecfd0.js → 5f6af561-f90b7a67f4f75d13.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6168.17749aa3905fa073.js +74 -0
- package/apps/pwa/out/_next/static/chunks/6252.240578f10952e091.js +34 -0
- package/apps/pwa/out/_next/static/chunks/6416.5d50b8ec9986ca98.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5101.e32282718f99ed6f.js → 6680.6a693c28aae4cd19.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{4718-17d2af636796a330.js → 6745-5a63492933959dd9.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/6758.e7fbf532953c0783.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6795.0cb8a59730d99703.js +149 -0
- package/apps/pwa/out/_next/static/chunks/{6236.e18cced2d5ba6afb.js → 6819.be5c9c4d67f22922.js} +16 -16
- package/apps/pwa/out/_next/static/chunks/6877-84d47b7e85d0c41e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2833-0fe47af99ed13c29.js → 6971-fb5f9dc0a2cb5d2d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{da5176c6.6773b6c5994e1d99.js → 709a918c.4fd3b4e2a32cb4e3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/717.522b08685979b814.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7191.e41eb8f455c7f089.js +41 -0
- package/apps/pwa/out/_next/static/chunks/7233.8e744b7513119409.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7238-97c2226f80504bd8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7285.307963afcf7e7660.js +24 -0
- package/apps/pwa/out/_next/static/chunks/7299-fbe4b8c47a87614a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7314.df0d95930b0c3f50.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7406.c0589fdf2ee66629.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7424.c0fe2a595d4232b5.js +23 -0
- package/apps/pwa/out/_next/static/chunks/752-90e3e7daad3d8e39.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7524-bcd3d9928c6842d9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7549.9a4de3bf231cfc52.js +1 -0
- package/apps/pwa/out/_next/static/chunks/757-4fbaf50942b8c424.js +1 -0
- package/apps/pwa/out/_next/static/chunks/765.76ae970ceb597b5b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7644.17e37ba6f1d4226e.js → 7867.e8725745ddd1d940.js} +4 -4
- package/apps/pwa/out/_next/static/chunks/7893-e444a1b0809cd1e4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7956-33748cabb3a05562.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{0368f25d-ad84af92fefee68f.js → 7c5ed21f-f286708e57cf88f7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8030-aa66b478b53dbfca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8051.c88c07dacbbc5f21.js +8 -0
- package/apps/pwa/out/_next/static/chunks/{359-2250789a2d3499e7.js → 8080-40d781e7704d3671.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8149.80e402d4345e8dae.js +77 -0
- package/apps/pwa/out/_next/static/chunks/8205.50a5268f4e12ba0b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8231.a128feaa6f8b6079.js +1 -0
- package/apps/pwa/out/_next/static/chunks/85.4ddb1dda2ea3ffa2.js +286 -0
- package/apps/pwa/out/_next/static/chunks/8537.26517a09e8247beb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8595-e9b7081ab773a61f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8821-30cc65a61a60951f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8905.b3385aadb7b133e6.js +79 -0
- package/apps/pwa/out/_next/static/chunks/8983.246bb3a1ac38f50f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{b2f6ba4d.fb56fdbbf798df33.js → 8c58d09b.e8724a299658d86f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9072.18342f5941a1f3ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9123.9df04866b2d6e00f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1530.0e612369b6fd7ea0.js → 9151.a2c51e93f9156dae.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{4441-875b97f5821ed58a.js → 9175-7d383df15a82e2be.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9181-bf89c07ce5b8f599.js +1 -0
- package/apps/pwa/out/_next/static/chunks/920-429bbc4504b45f71.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9207.625edf45d656151c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9378.d2575c6ffd6dd756.js +94 -0
- package/apps/pwa/out/_next/static/chunks/957.db5b5c4d32a35240.js +1 -0
- package/apps/pwa/out/_next/static/chunks/958.53a8dcf4729e6997.js +64 -0
- package/apps/pwa/out/_next/static/chunks/9647.8f0cd0898c592a29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9665-26abb02665704c2c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9703.e7d81b6faef1050a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9882-7a9164b3c2dacbe5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9998.b74e033efadecd10.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c604fb980a634d88.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-ec2d8a4979ca9075.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-cb6add08ad3c7660.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-e2eaa321ec223fe6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-5b8a09c475b5f844.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-91a77daedfbac191.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-f37fc5e974bda39b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-91429a0b78415a94.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-b95798b58263b910.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-763d53e175e17aa1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-d55023204055b12f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-89d2140d78abc810.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-82dc069abfe27f55.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-25d9eee7beb552c0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-770b4594c713ee97.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-c604fb980a634d88.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-ba8e0dcae4f83985.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-5c112c6d7c33656e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-09788e1aa9a87db6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-3337c6a1328c2674.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-89acd5d68c0bc560.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-5c13e2c76371d797.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-653a9f18642825c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-fe986fe79b107546.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-47c5d4db587266bd.js → page-1e5da297557d2354.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-91ea9f5869a40943.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-7824e21b999962e2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-53d00a60cc75a922.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-1f5a60f70cb4dd0c.js → page-88a1edbff62eaf1d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-4e931248b3f9574b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/{page-bac7dee94e5eae4a.js → page-443364c9e4a23b31.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/page-b9af2ac1c4737ce8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-25ab9c614c14129b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-dbcdcfac0a395a8a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-bf1de6645089ec58.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c4201761a5e839c9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-c304f469ea5d70e4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-8e5a238cf5e50c9c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-c604fb980a634d88.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-32b526069ee50b3c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-fb9e639c107f5f72.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-84220c776608cedc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-e475618cbfd5ca2b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/{page-dc0a532f5b9196ec.js → page-ac2c1e6169cfce57.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-fccd3020af2f8806.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-9c0d7198ff7b5a91.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-d54b1ec9b9c6430d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-8c4bef1791d732ca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-46fcc0b0bbc6fbfe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-c1c80e345fa9d6f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-7dfda00afd172f5b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{d06f9160.a3d9062f95de8ad7.js → d9815f84.4918f682a45a8502.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{cbe38a15.5a032598e39970c6.js → de8446d3.31b73eb239fecd7b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/f21fd540.63ac75328926ed30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/framework-77fdcc873519a6cd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-d0d620d2ce235713.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-e0ef79c79337ba90.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-84b1bc71e3f9ce6a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-2acb61f1363128cb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-462ff5727ceb7824.js +1 -0
- package/apps/pwa/out/_next/static/css/{b5d88eba78a3d7bf.css → be9019f696217521.css} +1 -1
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +23 -23
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +23 -23
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +23 -23
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +22 -22
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +22 -22
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +23 -23
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +22 -22
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +23 -23
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +23 -23
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +22 -22
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +23 -23
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +23 -23
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +23 -23
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +23 -23
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +22 -22
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +22 -22
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +23 -23
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +23 -23
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +23 -23
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +22 -22
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +22 -22
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +22 -22
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +23 -23
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +23 -23
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +22 -22
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +22 -22
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +22 -22
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +22 -22
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +23 -23
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +23 -23
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +22 -22
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +23 -23
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +23 -23
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +23 -23
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +23 -23
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +23 -23
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +22 -22
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +23 -23
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +23 -23
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +23 -23
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +23 -23
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +23 -23
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +23 -23
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +23 -23
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +23 -23
- package/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/drafts/recipes.yaml +27 -7
- 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/draft-close.yaml +54 -0
- package/graphs/work/green-close.yaml +37 -0
- package/graphs/work/recipes.yaml +46 -1
- package/graphs/work/research-report.yaml +2 -0
- package/graphs/work/run-research.yaml +35 -0
- package/package.json +1 -1
- package/scripts/__fixtures__/authoring-paths-10x3.json +32 -0
- package/scripts/boundary-proof-mapping-audit.ts +88 -0
- package/scripts/ci-typecheck-changed.ts +139 -5
- package/scripts/codex-fallback-live-probe.ts +17 -15
- package/scripts/coord-role-pack.ts +1 -1
- package/scripts/first10-gate.ts +416 -0
- package/scripts/fleet/node-setup.sh +32 -2
- package/scripts/fleet/run-light-worker.sh +9 -0
- package/scripts/fleet/wsl-node-setup.sh +432 -0
- package/scripts/gate-0226-failure-census.ts +507 -0
- package/scripts/goals/authoring-metrics.ts +258 -0
- package/scripts/graph-units/draft-close-node.ts +222 -0
- package/scripts/hook-order-sweep.ts +7 -4
- package/scripts/land-global-checks.ts +69 -1
- package/scripts/measure-nl-routing-corpus.ts +207 -2
- package/scripts/release-loop/docs-node.ts +344 -8
- package/scripts/release-loop/gate-closure.ts +48 -0
- package/scripts/release-loop/gate-diff.ts +2 -1
- package/scripts/release-loop/gate-node.ts +249 -53
- package/scripts/release-loop/known-issues-node.ts +6 -4
- package/scripts/release-loop/node-verdict.ts +2 -0
- package/scripts/release-loop/notes-repair.ts +216 -0
- package/scripts/release-loop/prepare-node.ts +61 -5
- package/scripts/release-loop/resume-cap.ts +14 -34
- package/scripts/release-loop/resume-release.ts +151 -15
- package/scripts/release-loop/resume-test-traffic.ts +10 -0
- package/scripts/release-loop/traffic-node.ts +59 -0
- package/scripts/release-loop/traffic-transition.ts +159 -0
- package/scripts/release-loop/version-node.ts +81 -12
- package/scripts/rhythm/daily.ts +137 -3
- package/scripts/train/green-leg.ts +176 -0
- package/scripts/tui-continue-real-evidence.ts +47 -0
- package/scripts/ux-sim.ts +53 -0
- package/skills/README.md +1 -0
- package/src/acp/client.ts +31 -5
- package/src/acp/core-turn-bridge.ts +54 -6
- package/src/acp/stderr-summary.ts +49 -0
- package/src/agent/delegate-hint.ts +16 -0
- package/src/agent/monad-agent-turn.ts +1 -0
- package/src/agent/runner.ts +82 -2
- package/src/agent/task-notification.ts +56 -10
- package/src/agent/terminal-surface.ts +217 -0
- package/src/agent/types.ts +4 -0
- package/src/agent-substrate/pr-reviewer.ts +64 -4
- 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 +490 -26
- package/src/autopilot/lineage/sources.ts +1 -1
- package/src/autopilot/mission-codebase-gate.ts +141 -12
- package/src/autopilot/pr-manager.ts +89 -18
- package/src/boot/daemon-runtime.ts +35 -3
- package/src/boot/daemon-tools/pty-shell.ts +2 -1
- package/src/boot/daemon-tools/self-implement.ts +1 -0
- package/src/budget/run-usage-rollup.ts +12 -7
- package/src/budget/unified-usage.ts +20 -0
- package/src/budget/usage-outcomes.ts +8 -4
- package/src/capture/tui-self-report.ts +132 -1
- 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/autopilot-cli.ts +24 -9
- package/src/cli/config-audit.ts +119 -0
- package/src/cli/decisions-cli.ts +7 -4
- package/src/cli/doctor-cli.ts +137 -6
- 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/logs-cli.ts +357 -2
- package/src/cli/make-cli.ts +247 -11
- package/src/cli/memory-cli.ts +30 -1
- package/src/cli/msg-cli.ts +132 -14
- package/src/cli/pr-cli.ts +29 -16
- package/src/cli/pr-granularity.ts +5 -3
- 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-checklist-read-cli.ts +109 -4
- package/src/cli/release-cli.ts +64 -15
- package/src/cli/release-single-flight.ts +1 -1
- package/src/cli/self-update.ts +23 -5
- package/src/cli/self-utterances-cli.ts +13 -16
- package/src/cli/tasks-card-show.ts +162 -0
- package/src/cli/tasks-cli.ts +868 -352
- package/src/cli/tasks-read-cli.ts +197 -0
- package/src/cli/usage-cli.ts +12 -10
- package/src/context-bus/claude-memory-import.ts +99 -0
- package/src/context-bus/long-term-memory.ts +125 -0
- package/src/core-turn/run-core-turn.ts +4 -2
- package/src/core-turn/run-goal-loop.ts +76 -16
- package/src/core-turn/types.ts +8 -1
- package/src/dashboard/chat/acp-chat.ts +9 -0
- package/src/dashboard/context/keys.ts +8 -31
- package/src/dashboard/index.ts +299 -62
- package/src/dashboard/input/agent-wake.ts +50 -1
- package/src/dashboard/input/chat-main-acp-dispatch.ts +21 -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/replay-preview.ts +25 -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 +20 -10
- package/src/dashboard/slash-runtime/model-choices.ts +15 -0
- package/src/dashboard/stale-install-check.ts +3 -99
- package/src/dashboard/synchronized-frame-output.ts +74 -0
- 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/display/coordinator.ts +45 -12
- package/src/display/resize-listener.ts +41 -20
- package/src/display/synchronized-output-region.ts +60 -0
- package/src/domains/standalone-log-sink.ts +15 -2
- package/src/domains/surface-events.ts +80 -1
- package/src/esc-abort-gate.ts +69 -0
- package/src/exec-requests/planner.ts +37 -2
- package/src/execution-loop/launch-gate.ts +4 -3
- package/src/git-fs/cli-stderr.ts +45 -0
- package/src/git-fs/gh-cli.ts +6 -3
- package/src/git-fs/git-cli.ts +11 -5
- package/src/git-fs/worktree.ts +95 -1
- package/src/graph-runner/graph-cli.ts +44 -2
- package/src/graph-runner/graph-run-api.ts +3 -2
- package/src/graph-runner/runner.ts +37 -16
- package/src/graph-wizard/generate.ts +131 -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 +230 -40
- package/src/harness/harness-incidents.ts +52 -1
- package/src/harness/pod-control-send.ts +33 -5
- package/src/harness/review-adapter.ts +26 -6
- 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 +443 -25
- package/src/input-core/context-key-runtime.ts +21 -0
- package/src/instance/test-write-guard-real-transport.fixture.ts +52 -0
- package/src/light-cli.ts +6 -1
- package/src/llm/route-decision.ts +13 -0
- package/src/llm-tool-name.ts +37 -0
- package/src/llm-tool-schema.ts +35 -0
- package/src/llm-wellformed.ts +70 -0
- package/src/llm.ts +158 -15
- package/src/loops/checker.ts +100 -0
- package/src/loops/consensus.ts +26 -2
- package/src/loops/loop-cli.ts +9 -4
- package/src/loops/msg-wake.ts +35 -39
- package/src/loops/orchestrator/anomaly-cluster.ts +243 -0
- package/src/loops/orchestrator/inbox.ts +59 -11
- package/src/loops/orchestrator/knowhow-pack.ts +364 -0
- package/src/loops/orchestrator/tick.ts +54 -5
- package/src/maturity/feature-maturity.ts +1 -1
- package/src/model-tier/llm-tier-map.ts +12 -17
- package/src/models/prompts.ts +17 -2
- package/src/msg/msg-post-office.ts +116 -0
- package/src/msg/msg-store.ts +17 -0
- package/src/mss/logging/log-store.ts +79 -22
- package/src/native-tool-catalog.ts +1 -1
- package/src/nexus/api/decisions-route.ts +38 -18
- package/src/nexus/api/graph-wizard-api.ts +46 -3
- package/src/nexus/api/grid.ts +24 -4
- package/src/nexus/api/http-server.ts +1 -1
- package/src/nexus/api/ops-api.ts +14 -6
- package/src/nexus/boot/register-mcp-clients.ts +263 -38
- package/src/nexus/onboarding/first-boot-wizard.ts +56 -8
- package/src/nexus/state/state.ts +7 -1
- package/src/oauth/codex-account-store.ts +34 -1
- package/src/onboarding/first-run.ts +37 -6
- package/src/onboarding/web-first.ts +43 -11
- package/src/ops/loops-map.ts +46 -0
- package/src/ops/overview-cache.ts +22 -4
- package/src/ops/overview-collect.ts +12 -6
- package/src/ops/overview.ts +44 -8
- package/src/persona/loader.ts +11 -0
- package/src/persona/types.ts +8 -0
- package/src/policy/llm-policy.ts +3 -1
- package/src/prompt-enhance/enhance.ts +4 -4
- package/src/prompt-library/session-guidance.ts +14 -0
- package/src/provider-summary.ts +8 -0
- package/src/pty-shell/availability.ts +7 -0
- package/src/pty-shell/herdr-agent-report.ts +155 -37
- package/src/pty-shell/herdr-prompt.ts +266 -0
- package/src/pty-shell/herdr-prune-stale.ts +17 -0
- package/src/pty-shell/herdr-sequence-lease.ts +60 -0
- package/src/pty-shell/pty-control-ipc.ts +327 -15
- package/src/pty-shell/pty-special-keys.ts +42 -2
- package/src/pty-shell/registry.ts +25 -15
- package/src/pty-shell/stale-install-check.ts +99 -0
- package/src/pty-shell/tui-run-state.ts +249 -3
- package/src/release-loop/auto-start.ts +20 -2
- package/src/release-loop/checklist.ts +126 -9
- package/src/release-loop/feature-store.ts +9 -9
- package/src/release-loop/landed-but-yellow.ts +93 -12
- package/src/release-loop/rehearsal.ts +36 -20
- package/src/repl/index.ts +27 -1
- package/src/seat-loop/knowhow-lint.ts +1 -1
- package/src/seat-loop/seat-report.ts +1 -0
- package/src/self-dev/ask-launch-flow.ts +86 -10
- package/src/self-dev/decompose-proposal.ts +183 -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 +37 -9
- package/src/self-dev/draft-sweep.ts +241 -16
- package/src/self-dev/draft-triage-rules.ts +50 -5
- 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/review-provider-fallback.ts +45 -7
- package/src/self-dev/run-supervisor.ts +55 -2
- package/src/self-dev/run-types.ts +1 -1
- package/src/self-dev/shard-plan.ts +48 -0
- package/src/self-dev/shard-run.ts +50 -3
- 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 +322 -1
- package/src/self-implement/evidence-command-budget.ts +80 -0
- package/src/self-implement/gate-artifacts.ts +6 -2
- package/src/self-implement/gate-baseline.ts +64 -1
- package/src/self-implement/gate-cli.ts +93 -9
- package/src/self-implement/gate-remote.ts +28 -4
- package/src/self-implement/gate-scope.ts +13 -1
- 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 +366 -26
- package/src/self-implement/goal-elaborate.ts +176 -22
- package/src/self-implement/goal-refine.ts +3 -0
- package/src/self-implement/goal-supervisor-fill.ts +75 -26
- package/src/self-implement/graph-variant.ts +40 -6
- package/src/self-implement/headless-elanous-driver.ts +19 -3
- package/src/self-implement/importer-test-index.ts +215 -2
- package/src/self-implement/knowhow-pack.ts +5 -5
- package/src/self-implement/off-diff-evidence.ts +58 -6
- package/src/self-implement/orchestrator.ts +536 -42
- 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/review-round-brief.ts +300 -0
- package/src/self-implement/rework-policy.ts +44 -0
- package/src/self-implement/run-ledger.ts +278 -6
- package/src/self-implement/run-stop.ts +1 -1
- package/src/self-implement/run-ttl.ts +38 -24
- package/src/self-implement/running-runs.ts +17 -3
- package/src/self-implement/scope-adjacency.ts +3 -1
- package/src/self-implement/seams.ts +72 -10
- package/src/self-implement/stop-autoheal.ts +1 -0
- package/src/self-implement/test-seams.ts +13 -1
- package/src/self-implement/train-reach-grouping.ts +8 -2
- package/src/self-implement/wiring-check.ts +358 -0
- package/src/session/chat.ts +7 -1
- package/src/session/id-prefix.ts +60 -0
- package/src/session/index.ts +4 -0
- package/src/session-runtime/index.ts +9 -1
- 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/pty.ts +5 -0
- 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/metrics.ts +12 -1
- package/src/status/status-provider-info.ts +37 -6
- package/src/steward/rejudge-tick.ts +22 -7
- package/src/steward/resync-tick.ts +31 -4
- package/src/steward/ta-repair-event-collect.ts +17 -6
- package/src/task-agent/actions.ts +202 -5
- package/src/task-agent/boundary-proof-mapping.ts +12 -0
- package/src/task-agent/boundary-proof-producer.ts +147 -0
- package/src/task-agent/completion-evidence.ts +2 -1
- package/src/task-agent/green-close-recipe.ts +239 -0
- package/src/task-agent/green-sweep.ts +64 -10
- package/src/task-agent/land-queue.ts +21 -8
- package/src/task-agent/live-moves.ts +70 -9
- package/src/task-agent/map-host-adapter.ts +134 -0
- package/src/task-agent/map-reduce.ts +158 -0
- package/src/task-agent/map-run-ledger.ts +226 -0
- package/src/task-agent/mission.ts +30 -0
- package/src/task-agent/parent-host.ts +21 -11
- package/src/task-agent/rejudge-on-head.ts +87 -5
- package/src/task-agent/repair-cycle.ts +179 -32
- package/src/task-agent/repair-next-move.ts +42 -4
- package/src/task-agent/repair-outcomes.ts +29 -0
- package/src/task-agent/repair-worker.ts +14 -4
- package/src/task-agent/review-prior-fix.ts +12 -2
- package/src/task-agent/shadow.ts +229 -8
- package/src/task-agent/task-card-prefix.ts +2 -0
- package/src/task-agent/task-hand.ts +630 -30
- package/src/task-agent/task-parent-role-llm.ts +45 -0
- package/src/task-agent/tier-split.ts +80 -10
- package/src/task-agent/work-order.ts +53 -2
- package/src/task-orchestrator/dispatcher.ts +46 -1
- package/src/task-orchestrator/generator-prompt.ts +6 -0
- package/src/task-orchestrator/surfaces/pod-account-broker.ts +9 -0
- package/src/task-orchestrator/surfaces/pod-artifact-return.ts +16 -6
- 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-ledger-collect.ts +8 -6
- package/src/task-orchestrator/surfaces/pod-ledger-events.ts +97 -0
- package/src/task-orchestrator/surfaces/pod-manifest-test-normalize.ts +11 -0
- package/src/task-orchestrator/surfaces/pod-pool.ts +126 -8
- 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 +491 -36
- package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +3 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +612 -54
- package/src/task-orchestrator/surfaces/self-implement.ts +46 -2
- package/src/testing/linux-env.ts +177 -0
- 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 +177 -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 +623 -80
- package/src/tui/input/at-picker.ts +55 -0
- 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 +98 -0
- package/src/tui/slash/signals.ts +114 -0
- package/src/tui/slash/term-next.ts +71 -0
- package/src/tui-client/local-chat-turn.ts +49 -5
- 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 +19 -1
- package/src/ux-sim/boot-daemon-tools-pty-shell-device.ts +136 -0
- package/src/ux-sim/classic-tui-seat-scenarios.ts +670 -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-gh.ts +174 -0
- package/src/ux-sim/fake-herdr.ts +238 -0
- package/src/ux-sim/fake-kubectl.ts +142 -0
- package/src/ux-sim/fake-llm.ts +116 -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 +749 -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/resume-picker-device.ts +173 -0
- package/src/ux-sim/session-id-seed.ts +60 -0
- package/src/ux-sim/skills-tools-shell-device.ts +156 -0
- package/src/ux-sim/subagent-tools-device.ts +81 -0
- package/src/ux-sim/task-agent-parent-host-device.ts +264 -0
- package/src/ux-sim/task-agent-rejudge-on-head-device.ts +114 -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/1540.af38731fa7c4a3e0.js +0 -41
- package/apps/pwa/out/_next/static/chunks/1674.2a65fd5668e1cc5f.js +0 -1
- 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/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/2930.d0edb4f108787ef7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2973.77318e7d85f59477.js +0 -62
- package/apps/pwa/out/_next/static/chunks/3103.0df8da390948bb84.js +0 -1
- 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/3589.5444a06bcf6e775c.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/3677-4215f826f6aa68e5.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/4182-0b9b1251c29cc0cf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4346.8601952e73bf5803.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/5637.281e5dec4ea6ecf1.js +0 -94
- package/apps/pwa/out/_next/static/chunks/5662.02eaa029af05ad46.js +0 -1
- package/apps/pwa/out/_next/static/chunks/56d372f2.681ab4ec4c998fae.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/630.fe5a46428d63b013.js +0 -1
- 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/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/8637.47f5b35afcae5e6b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8714.ff0b7ce3ec3e3ae2.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/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/seats/page-fbad60d1e358fb2c.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/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 → 8kITumG9GAZ1LwT9POrAR}/_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, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { tmpdir, userInfo } from 'node:os';
|
|
6
|
+
import { dlopen, read, type Pointer } 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,121 @@ 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 prepareErrnoLocation = prepareLockLib?.symbols[process.platform === 'linux' ? '__errno_location' : '__error'];
|
|
169
|
+
const readPrepareErrno = (): number => (prepareErrnoLocation ? read.i32(prepareErrnoLocation() as Pointer) : -1);
|
|
170
|
+
const LOCK_EX = 2;
|
|
171
|
+
const LOCK_NB = 4;
|
|
172
|
+
const LOCK_UN = 8;
|
|
173
|
+
const PREPARE_LOCK_WAIT_MS = (POD_READY_WAIT_SECONDS * 1_000) + 30_000;
|
|
174
|
+
|
|
175
|
+
async function withPrepareLease<T>(candidate: WarmWorkerCandidate, directory: string | undefined,
|
|
176
|
+
work: () => Promise<T>, signal?: AbortSignal): Promise<T | null> {
|
|
177
|
+
const uid = typeof process.getuid === 'function' ? process.getuid() : userInfo().uid;
|
|
178
|
+
const root = directory ?? join(tmpdir(), `elanous-warm-prepare-${uid}`);
|
|
179
|
+
const identity = createHash('sha256').update(`${candidate.host}/${candidate.spec.workerId}`).digest('hex');
|
|
180
|
+
let fd: number | undefined;
|
|
181
|
+
let acquired = false;
|
|
182
|
+
try {
|
|
183
|
+
checkedPrivateDirectory(root);
|
|
184
|
+
const lock = join(root, `${identity}.flock`);
|
|
185
|
+
fd = openSync(lock, constants.O_CREAT | constants.O_RDWR | (constants.O_NOFOLLOW ?? 0), 0o600);
|
|
186
|
+
const info = lstatSync(lock);
|
|
187
|
+
if (!info.isFile() || info.isSymbolicLink() || info.uid !== uid || (info.mode & 0o077) !== 0)
|
|
188
|
+
throw new Error('unsafe warm prepare lock file');
|
|
189
|
+
if (!prepareFlock) throw new Error('warm prepare OS locks are unavailable on this platform');
|
|
190
|
+
const lockDeadline = performance.now() + PREPARE_LOCK_WAIT_MS;
|
|
191
|
+
while (!acquired) {
|
|
192
|
+
if (signal?.aborted) throw new Error('warm prepare lock wait cancelled');
|
|
193
|
+
if (prepareFlock(fd, LOCK_EX | LOCK_NB) === 0) { acquired = true; break; }
|
|
194
|
+
const errno = readPrepareErrno();
|
|
195
|
+
// EWOULDBLOCK/EAGAIN: Linux 11, Darwin 35. EINTR is safe to retry.
|
|
196
|
+
if (errno !== 11 && errno !== 35 && errno !== 4)
|
|
197
|
+
throw new Error(`warm prepare flock failed (errno ${errno})`);
|
|
198
|
+
if (performance.now() >= lockDeadline) throw new Error('warm prepare lock wait timed out');
|
|
199
|
+
await new Promise<void>((resolve, reject) => {
|
|
200
|
+
const timer = setTimeout(done, Math.min(20, Math.max(1, lockDeadline - performance.now())));
|
|
201
|
+
const abort = () => { clearTimeout(timer); signal?.removeEventListener('abort', abort); reject(new Error('warm prepare lock wait cancelled')); };
|
|
202
|
+
function done() { signal?.removeEventListener('abort', abort); resolve(); }
|
|
203
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return await work();
|
|
207
|
+
} catch (error) {
|
|
208
|
+
debug.log('pod.warm', 'prepare-lease-failed', { host: candidate.host,
|
|
209
|
+
workerId: candidate.spec.workerId, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' });
|
|
210
|
+
throw error;
|
|
211
|
+
} finally {
|
|
212
|
+
if (acquired && fd !== undefined) prepareFlock?.(fd, LOCK_UN);
|
|
213
|
+
if (fd !== undefined) closeSync(fd);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
45
217
|
export class WarmPodProvider {
|
|
46
218
|
private readonly fetcher: (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
47
219
|
private readonly now: () => number;
|
|
220
|
+
private readonly candidates: readonly WarmWorkerCandidate[];
|
|
221
|
+
private readonly profile: WarmPoolProfile;
|
|
222
|
+
/** This process's own legs, echoed on CLI stdout. Pairing never reads this — see readWarmReuseTimePairs. */
|
|
223
|
+
private readonly legs: WarmReuseTimeLegRow[] = [];
|
|
224
|
+
/** One terminal transition per fenced lease, including concurrent callers. Failed transitions remain retryable. */
|
|
225
|
+
private readonly terminalLeases = new Map<string, Promise<'sanitized' | 'quarantined'>>();
|
|
226
|
+
private readonly standingWorkers = new Map<string, Promise<boolean>>();
|
|
48
227
|
constructor(private readonly options: WarmPodProviderOptions) {
|
|
49
228
|
const url = new URL(options.controlUrl);
|
|
50
229
|
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.search || url.hash
|
|
51
230
|
|| url.pathname !== '/' || !token(options.memberToken)) throw new Error('invalid warm control address');
|
|
52
|
-
|
|
53
|
-
|
|
231
|
+
const profile = resolveWarmPoolProfile(options.profileConfig ?? getUserConfig());
|
|
232
|
+
this.profile = profile;
|
|
233
|
+
const templates = options.poolTemplates ?? [];
|
|
234
|
+
if (new Set(templates.map(candidate => `${candidate.host}/${candidate.tier}`)).size !== templates.length)
|
|
235
|
+
throw new Error('duplicate warm pool template');
|
|
236
|
+
const expanded = templates.flatMap(candidate => {
|
|
237
|
+
const quota = resolveWarmPoolQuota(profile, candidate.host, candidate.tier);
|
|
238
|
+
if (!quota || !candidate.spec.nodeName) throw new Error('unmeasured warm pool template');
|
|
239
|
+
return Array.from({ length: quota }, (_, index): WarmWorkerCandidate => ({
|
|
240
|
+
host: candidate.host, tier: candidate.tier, spec: {
|
|
241
|
+
...candidate.spec,
|
|
242
|
+
name: `warm-${candidate.host}-${candidate.tier}-${index + 1}`,
|
|
243
|
+
// The template worker ID is a generation token. Rotate it when a slot is quarantined.
|
|
244
|
+
workerId: `${candidate.spec.workerId}-${candidate.tier}-${index + 1}`,
|
|
245
|
+
},
|
|
246
|
+
}));
|
|
247
|
+
});
|
|
248
|
+
this.candidates = [...options.candidates, ...expanded];
|
|
249
|
+
if (this.candidates.some(candidate => !workerId(candidate.spec.workerId)
|
|
250
|
+
|| !candidate.host.trim() || resolveWarmPoolQuota(profile, candidate.host, candidate.tier) < 1
|
|
54
251
|
|| candidate.spec.memoryLimit !== (candidate.tier === 'lite' ? '2Gi' : '16Gi')
|
|
55
252
|
|| !warmWorkerPodManifest(candidate.spec))) throw new Error('invalid warm candidate');
|
|
56
|
-
if (new Set(
|
|
253
|
+
if (new Set(this.candidates.map(candidate => candidate.spec.workerId)).size !== this.candidates.length)
|
|
57
254
|
throw new Error('duplicate warm worker ID');
|
|
255
|
+
const names = new Set<string>();
|
|
256
|
+
const counts = new Map<string, number>();
|
|
257
|
+
for (const candidate of this.candidates) {
|
|
258
|
+
const identity = `${candidate.spec.kubectlContext}/${candidate.spec.namespace}/${candidate.spec.name}`;
|
|
259
|
+
if (names.has(identity)) throw new Error('duplicate warm Pod name');
|
|
260
|
+
names.add(identity);
|
|
261
|
+
const slot = `${candidate.host}/${candidate.tier}`;
|
|
262
|
+
const count = (counts.get(slot) ?? 0) + 1;
|
|
263
|
+
if (count > resolveWarmPoolQuota(profile, candidate.host, candidate.tier))
|
|
264
|
+
throw new Error('warm candidate quota exceeded');
|
|
265
|
+
counts.set(slot, count);
|
|
266
|
+
}
|
|
58
267
|
this.fetcher = options.fetch ?? fetch;
|
|
59
268
|
this.now = options.now ?? Date.now;
|
|
60
269
|
}
|
|
@@ -118,14 +327,61 @@ export class WarmPodProvider {
|
|
|
118
327
|
} finally { rmSync(directory, { recursive: true, force: true }); }
|
|
119
328
|
}
|
|
120
329
|
|
|
121
|
-
/**
|
|
330
|
+
/** Emit one measurement leg into the shared logs.db row the other process's leg joins. */
|
|
331
|
+
private recordPair(leg: 'cold' | 'warm', file: string, commit: string, host: string,
|
|
332
|
+
row: Omit<WarmPreparePairLeg, 'file' | 'commit' | 'host'>): void {
|
|
333
|
+
const legRow: WarmReuseTimeLegRow = { gateKey: `${file}/${commit}/${host}`, leg, file, commit, host, ...row };
|
|
334
|
+
this.legs.push(legRow);
|
|
335
|
+
debug.log(PAIR_CATEGORY, PAIR_EVENT, legRow, { level: 'info' });
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** The legs this process emitted (stdout echo only — pairing is readWarmReuseTimePairs over logs.db). */
|
|
339
|
+
warmReuseTimeLegs(): readonly WarmReuseTimeLegRow[] { return [...this.legs]; }
|
|
340
|
+
|
|
341
|
+
/** Prepare one named slot. A foreign or replaced Pod is never deleted. */
|
|
122
342
|
async ensureStandingWorker(candidate: WarmWorkerCandidate): Promise<boolean> {
|
|
123
|
-
const configured = this.
|
|
343
|
+
const configured = this.candidates.find(row => row.host === candidate.host
|
|
344
|
+
&& row.spec.workerId === candidate.spec.workerId);
|
|
345
|
+
if (!configured) return false;
|
|
346
|
+
const key = `${configured.host}/${configured.spec.workerId}`;
|
|
347
|
+
const inFlight = this.standingWorkers.get(key);
|
|
348
|
+
if (inFlight) return inFlight;
|
|
349
|
+
const preparation = withPrepareLease(configured, this.options.prepareLockDirectory,
|
|
350
|
+
() => this.prepareStandingWorker(configured, this.options.prepareAbortSignal), this.options.prepareAbortSignal)
|
|
351
|
+
.then(result => result === true);
|
|
352
|
+
this.standingWorkers.set(key, preparation);
|
|
353
|
+
try { return await preparation; }
|
|
354
|
+
finally { if (this.standingWorkers.get(key) === preparation) this.standingWorkers.delete(key); }
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
private async prepareStandingWorker(candidate: WarmWorkerCandidate, signal?: AbortSignal): Promise<boolean> {
|
|
358
|
+
throwIfPrepareAborted(signal);
|
|
359
|
+
const startedAt = this.now();
|
|
360
|
+
const outcome: { preexisting: boolean; podUid: string | null } = { preexisting: false, podUid: null };
|
|
361
|
+
const ok = await this.coldPrepare(candidate, outcome, signal);
|
|
362
|
+
const totalDurationMs = this.now() - startedAt;
|
|
363
|
+
const gateFile = this.options.pairingGateFile;
|
|
364
|
+
// An unset gate file skips the measurement row entirely instead of recording an unpaired one.
|
|
365
|
+
if (gateFile) this.recordPair('cold', gateFile, candidate.spec.cutSha, candidate.host,
|
|
366
|
+
{ reuse: outcome.preexisting, prepareDurationMs: outcome.preexisting ? 0 : totalDurationMs,
|
|
367
|
+
totalDurationMs, podUid: outcome.podUid ?? '', success: ok });
|
|
368
|
+
return ok;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
private async coldPrepare(candidate: WarmWorkerCandidate,
|
|
372
|
+
outcome: { preexisting: boolean; podUid: string | null }, signal?: AbortSignal): Promise<boolean> {
|
|
373
|
+
const configured = this.candidates.find(row => row.host === candidate.host
|
|
124
374
|
&& row.spec.workerId === candidate.spec.workerId && row.spec.cutSha === candidate.spec.cutSha
|
|
125
375
|
&& keyEquals(row.spec.key, candidate.spec.key));
|
|
126
|
-
//
|
|
127
|
-
|
|
376
|
+
// Legacy slot warm-HOST and numbered profile slots can coexist during migration.
|
|
377
|
+
const prefix = configured ? `warm-${configured.host}` : '';
|
|
378
|
+
const suffix = configured?.spec.name.startsWith(`${prefix}-`)
|
|
379
|
+
? configured.spec.name.slice(prefix.length + 1) : '';
|
|
380
|
+
if (!configured || !configured.spec.nodeName
|
|
381
|
+
|| (configured.spec.name !== prefix && !/^[1-9][0-9]*$/.test(suffix)
|
|
382
|
+
&& !/^(lite|standard)-[1-9][0-9]*$/.test(suffix))
|
|
128
383
|
|| candidate.spec.name !== configured.spec.name || candidate.spec.nodeName !== configured.spec.nodeName) return false;
|
|
384
|
+
throwIfPrepareAborted(signal);
|
|
129
385
|
const { spec, host } = configured;
|
|
130
386
|
const nodeName = spec.nodeName;
|
|
131
387
|
if (!nodeName) return false;
|
|
@@ -206,7 +462,19 @@ export class WarmPodProvider {
|
|
|
206
462
|
if (hostPods.length > 1) return false;
|
|
207
463
|
let pod = hostPods[0];
|
|
208
464
|
const preexisting = !!pod;
|
|
465
|
+
outcome.preexisting = preexisting;
|
|
209
466
|
if (pod && !matches(pod)) return false;
|
|
467
|
+
// Old generations still occupy memory even when their ledger rows are quarantined.
|
|
468
|
+
// Count physical warm Pods on the pinned node before admitting a new one.
|
|
469
|
+
const physical = listed.items.filter((row: any) => row?.metadata?.labels?.['elanous.kind'] === 'warm-worker'
|
|
470
|
+
&& row?.spec?.nodeName === nodeName && !row?.metadata?.deletionTimestamp
|
|
471
|
+
&& row?.status?.phase !== 'Succeeded' && row?.status?.phase !== 'Failed'
|
|
472
|
+
&& row?.spec?.containers?.[0]?.resources?.limits?.memory === spec.memoryLimit).length;
|
|
473
|
+
if (!preexisting && physical >= resolveWarmPoolQuota(this.profile, host, configured.tier)) {
|
|
474
|
+
debug.log('pod.warm', 'pool-slot-refused', { host, tier: configured.tier,
|
|
475
|
+
workerId: spec.workerId, reason: 'physical-quota', physical }, { level: 'warn' });
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
210
478
|
const readLedger = async (): Promise<any[] | null> => {
|
|
211
479
|
try {
|
|
212
480
|
const query = new URLSearchParams({ kind: 'pod-worker-lease', machine: host, name: spec.workerId });
|
|
@@ -218,15 +486,31 @@ export class WarmPodProvider {
|
|
|
218
486
|
if (!pod) {
|
|
219
487
|
// A worker ID already bound to another UID must not leave a newly created, unregistered Pod.
|
|
220
488
|
const before = await readLedger();
|
|
489
|
+
throwIfPrepareAborted(signal);
|
|
221
490
|
if (!before || before.length !== 0) return false;
|
|
222
491
|
pod = parse(kubectl(['create', '-f', '-', '-o', 'json'], JSON.stringify(manifest)));
|
|
223
492
|
if (!matches(pod)) return false;
|
|
224
493
|
}
|
|
225
494
|
const uid = pod.metadata.uid as string;
|
|
226
|
-
|
|
227
|
-
const ready =
|
|
228
|
-
|
|
229
|
-
|
|
495
|
+
outcome.podUid = uid;
|
|
496
|
+
const ready = (value: any) => matches(value) && value.metadata.uid === uid
|
|
497
|
+
&& value.status?.phase === 'Running'
|
|
498
|
+
&& value.status?.conditions?.some((row: any) => row.type === 'Ready' && row.status === 'True');
|
|
499
|
+
const deadline = this.now() + POD_READY_WAIT_SECONDS * 1000;
|
|
500
|
+
let isReady = false;
|
|
501
|
+
while (this.now() < deadline) {
|
|
502
|
+
const remainingSeconds = Math.ceil((deadline - this.now()) / 1000);
|
|
503
|
+
const waitSeconds = Math.min(POD_READY_RECHECK_SECONDS, remainingSeconds);
|
|
504
|
+
throwIfPrepareAborted(signal);
|
|
505
|
+
kubectl(['wait', `pod/${spec.name}`, '--for=condition=Ready', `--timeout=${waitSeconds}s`]);
|
|
506
|
+
throwIfPrepareAborted(signal);
|
|
507
|
+
const observed = parse(kubectl(['get', `pod/${spec.name}`, '-o', 'json']));
|
|
508
|
+
throwIfPrepareAborted(signal);
|
|
509
|
+
if (ready(observed)) { isReady = true; break; }
|
|
510
|
+
if (!matches(observed) || observed.metadata.uid !== uid) return false;
|
|
511
|
+
if (this.now() < deadline) await waitForPrepareRetry(signal);
|
|
512
|
+
}
|
|
513
|
+
if (!isReady) return false;
|
|
230
514
|
// Re-read after create: a registration racing the preflight must fail before our POST.
|
|
231
515
|
const rows = await readLedger();
|
|
232
516
|
if (!rows) return false;
|
|
@@ -244,6 +528,7 @@ export class WarmPodProvider {
|
|
|
244
528
|
// An unregistered Pod could have been created by anyone with namespace access.
|
|
245
529
|
// Only this invocation's successful create response may become a new ledger row.
|
|
246
530
|
if (preexisting) return false;
|
|
531
|
+
throwIfPrepareAborted(signal);
|
|
247
532
|
let registered: { status: number; value: any };
|
|
248
533
|
try {
|
|
249
534
|
registered = await this.control('/v1/workers/pod', credential, {
|
|
@@ -258,27 +543,60 @@ export class WarmPodProvider {
|
|
|
258
543
|
}
|
|
259
544
|
|
|
260
545
|
async prepareWorker(workerId: string): Promise<boolean> {
|
|
261
|
-
const candidate = this.
|
|
546
|
+
const candidate = this.candidates.find(row => row.spec.workerId === workerId);
|
|
262
547
|
if (!candidate) return false;
|
|
263
548
|
return this.ensureStandingWorker(candidate);
|
|
264
549
|
}
|
|
265
550
|
|
|
551
|
+
/** One bounded reconciliation pass. Failed slots stay unavailable until a fresh worker ID is configured. */
|
|
552
|
+
async reconcilePool(): Promise<WarmPoolReconcileResult> {
|
|
553
|
+
const failed: string[] = [];
|
|
554
|
+
let ready = 0;
|
|
555
|
+
for (const candidate of this.candidates) {
|
|
556
|
+
let prepared = false;
|
|
557
|
+
try { prepared = await this.ensureStandingWorker(candidate); }
|
|
558
|
+
catch { /* Uncertain state is never counted as ready. */ }
|
|
559
|
+
if (prepared) ready++;
|
|
560
|
+
else failed.push(candidate.spec.workerId);
|
|
561
|
+
debug.log('pod.warm', 'pool-slot', { profile: this.profile,
|
|
562
|
+
host: candidate.host, tier: candidate.tier, workerId: candidate.spec.workerId,
|
|
563
|
+
pod: candidate.spec.name, ready: prepared }, { level: prepared ? 'info' : 'warn' });
|
|
564
|
+
}
|
|
565
|
+
const result = { profile: this.profile,
|
|
566
|
+
target: this.candidates.length, ready, failed };
|
|
567
|
+
debug.log('pod.warm', 'pool-reconcile', result, { level: failed.length ? 'warn' : 'info' });
|
|
568
|
+
return result;
|
|
569
|
+
}
|
|
570
|
+
|
|
266
571
|
/** Acquire a matching idle worker before exec. Every uncertain result is fenced out of reuse. */
|
|
267
572
|
async request(input: WarmReplayInput): Promise<WarmReplayResult> {
|
|
573
|
+
const requestedAt = this.now();
|
|
268
574
|
if (input.protocol !== 1 || input.envProfile !== 'gate-v1' || !Array.isArray(input.files)
|
|
269
575
|
|| !input.files.length || !Number.isFinite(input.deadlineAt) || input.deadlineAt <= this.now()) return fallback('no-candidate');
|
|
270
|
-
const
|
|
271
|
-
if (!
|
|
576
|
+
const matches = this.candidates.filter(row => row.spec.cutSha === input.cutSha);
|
|
577
|
+
if (!matches.length) return fallback('no-candidate');
|
|
578
|
+
let lastFallback: WarmReplayResult = fallback('lease-unavailable');
|
|
579
|
+
// The control plane chooses an idle worker within a host/key. Probe each distinct
|
|
580
|
+
// host/key once so a busy first host cannot strand an idle second pool.
|
|
581
|
+
const tried = new Set<string>();
|
|
582
|
+
for (const candidate of matches) {
|
|
272
583
|
const { spec, host } = candidate;
|
|
584
|
+
const poolKey = `${host}/${spec.key.context}/${spec.key.imageDigest}/${spec.key.lockHash}/${spec.key.architecture}`;
|
|
585
|
+
if (tried.has(poolKey)) continue;
|
|
586
|
+
tried.add(poolKey);
|
|
273
587
|
const contextFailure = probeWarmPodContext(spec.kubectlContext, this.options.kubectl);
|
|
274
|
-
if (contextFailure)
|
|
588
|
+
if (contextFailure) { lastFallback = { command: null, transport: contextFailure }; continue; }
|
|
275
589
|
let acquired: { status: number; value: any };
|
|
276
590
|
try {
|
|
277
591
|
acquired = await this.control('/v1/leases/pod-worker', this.options.memberToken, {
|
|
278
592
|
machine: host, key: spec.key, runId: input.runId, attemptId: input.attemptId,
|
|
279
593
|
ttlMs: this.options.leaseTtlMs ?? 30 * 60_000,
|
|
280
594
|
});
|
|
281
|
-
} catch {
|
|
595
|
+
} catch { lastFallback = fallback('lease-unavailable'); continue; }
|
|
596
|
+
if (acquired.status === 409 || acquired.status === 503) {
|
|
597
|
+
lastFallback = fallback('lease-unavailable');
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
282
600
|
if (acquired.status !== 200) return fallback('lease-unavailable');
|
|
283
601
|
const row = acquired.value;
|
|
284
602
|
const leasedId = row?.attrs?.leaseId;
|
|
@@ -289,7 +607,7 @@ export class WarmPodProvider {
|
|
|
289
607
|
|| !Number.isSafeInteger(generation) || generation < 1) return fallback('lease-unavailable');
|
|
290
608
|
// Acquisition selects by host/key, not by worker ID. Resolve the returned worker
|
|
291
609
|
// before choosing its transport; an unknown worker must not be executed or fenced.
|
|
292
|
-
const leasedCandidate = this.
|
|
610
|
+
const leasedCandidate = this.candidates.find(entry => entry.host === host
|
|
293
611
|
&& entry.spec.workerId === row.name && entry.spec.cutSha === input.cutSha
|
|
294
612
|
&& keyEquals(entry.spec.key, spec.key));
|
|
295
613
|
if (!leasedCandidate) {
|
|
@@ -305,6 +623,7 @@ export class WarmPodProvider {
|
|
|
305
623
|
return fallback('lease-unavailable');
|
|
306
624
|
}
|
|
307
625
|
const command: WarmWorkerCommand = { ...input, leaseId: leasedId, generation, podUid,
|
|
626
|
+
podName: leasedCandidate.spec.name, podContext: leasedCandidate.spec.kubectlContext, image: leasedCandidate.spec.image,
|
|
308
627
|
requestedAt: new Date(this.now()).toISOString(),
|
|
309
628
|
workerId: row.name, key: leasedCandidate.spec.key };
|
|
310
629
|
const admitted = patchWarmLeaseMetadata(leasedCandidate.spec, command, 'leased', this.options.kubectl);
|
|
@@ -320,6 +639,16 @@ export class WarmPodProvider {
|
|
|
320
639
|
let transport: WarmPodTransportResult;
|
|
321
640
|
try { transport = await this.runTransport(leasedCandidate.spec, command); }
|
|
322
641
|
catch { transport = { status: 'fallback', reason: 'exec-failed', reply: null }; }
|
|
642
|
+
// Warm leg: the lease reused an already-standing Pod, so no prepare interval elapsed.
|
|
643
|
+
// Only a 1-file gate has a pairing key; a multi-file gate is skipped (with its reason), never keyed by files[0].
|
|
644
|
+
const gateFile = command.files.length === 1 ? command.files[0] : undefined;
|
|
645
|
+
if (command.files.length !== 1) debug.log(PAIR_CATEGORY, `${PAIR_EVENT}-skipped`, { reason: 'not-single-file-gate',
|
|
646
|
+
fileCount: command.files.length, commit: command.cutSha, host }, { level: 'info' });
|
|
647
|
+
if (gateFile) this.recordPair('warm', gateFile, command.cutSha, host, {
|
|
648
|
+
reuse: true, prepareDurationMs: 0,
|
|
649
|
+
totalDurationMs: this.now() - requestedAt, podUid,
|
|
650
|
+
success: transport.status === 'observed' && transport.reason === 'complete',
|
|
651
|
+
});
|
|
323
652
|
if (transport.status !== 'observed' || transport.reason !== 'complete') {
|
|
324
653
|
await this.quarantine(command, transport.reason);
|
|
325
654
|
return { command, transport };
|
|
@@ -327,6 +656,8 @@ export class WarmPodProvider {
|
|
|
327
656
|
// Keep the lease fenced until the gate has compared these bytes with its Job baseline.
|
|
328
657
|
// A missing finalization leaves the worker leased and therefore unavailable.
|
|
329
658
|
return { command, transport };
|
|
659
|
+
}
|
|
660
|
+
return lastFallback;
|
|
330
661
|
}
|
|
331
662
|
|
|
332
663
|
private async transition(command: WarmWorkerCommand, action: 'release' | 'sanitize' | 'quarantine',
|
|
@@ -340,7 +671,25 @@ export class WarmPodProvider {
|
|
|
340
671
|
}
|
|
341
672
|
|
|
342
673
|
async finalize(command: WarmWorkerCommand): Promise<void> {
|
|
343
|
-
const
|
|
674
|
+
const key = JSON.stringify([command.workerId, command.leaseId, command.generation, command.podUid]);
|
|
675
|
+
const prior = this.terminalLeases.get(key);
|
|
676
|
+
if (prior) {
|
|
677
|
+
const outcome = await prior;
|
|
678
|
+
debug.log('pod.provenance', 'lease-terminal-suppressed', { workerId: command.workerId,
|
|
679
|
+
leaseId: command.leaseId, generation: command.generation, requested: 'finalize', outcome });
|
|
680
|
+
if (outcome === 'quarantined') throw new Error('warm lease already quarantined');
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
const terminal = this.finalizeOnce(command);
|
|
684
|
+
this.terminalLeases.set(key, terminal);
|
|
685
|
+
let outcome: 'sanitized' | 'quarantined';
|
|
686
|
+
try { outcome = await terminal; }
|
|
687
|
+
catch (error) { if (this.terminalLeases.get(key) === terminal) this.terminalLeases.delete(key); throw error; }
|
|
688
|
+
if (outcome === 'quarantined') throw new Error('warm sanitize unavailable');
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
private async finalizeOnce(command: WarmWorkerCommand): Promise<'sanitized' | 'quarantined'> {
|
|
692
|
+
const candidate = this.candidates.find(row => row.spec.workerId === command.workerId);
|
|
344
693
|
if (!candidate) throw new Error('warm finalize candidate missing');
|
|
345
694
|
const released = await this.transition(command, 'release');
|
|
346
695
|
const machineToken = this.options.machineTokens?.[candidate.host];
|
|
@@ -350,20 +699,36 @@ export class WarmPodProvider {
|
|
|
350
699
|
if (!cleared.ok) debug.log('pod.provenance', 'lease-patch-refused', { pod: candidate.spec.name,
|
|
351
700
|
podUid: command.podUid, leaseId: command.leaseId, reason: cleared.reason }, { level: 'warn' });
|
|
352
701
|
if (!cleared.ok || !token(machineToken) || !await this.transition(command, 'sanitize', machineToken)) {
|
|
353
|
-
await this.
|
|
354
|
-
|
|
702
|
+
await this.quarantineOnce(command, 'sanitize-unavailable', true);
|
|
703
|
+
return 'quarantined';
|
|
355
704
|
}
|
|
356
705
|
debug.log('pod.provenance', 'lease-end', { pod: candidate.spec.name, podUid: command.podUid,
|
|
357
706
|
leaseId: command.leaseId, runId: command.runId, attemptId: command.attemptId,
|
|
358
707
|
releasedAt: new Date(this.now()).toISOString(), result: 'sanitized' });
|
|
708
|
+
return 'sanitized';
|
|
359
709
|
}
|
|
360
710
|
|
|
361
711
|
/** `leaseStarted=false` marks a refusal before `lease-start`, so start/end history stays paired. */
|
|
362
712
|
async quarantine(command: WarmWorkerCommand, reason: string, leaseStarted = true): Promise<void> {
|
|
713
|
+
const key = JSON.stringify([command.workerId, command.leaseId, command.generation, command.podUid]);
|
|
714
|
+
const prior = this.terminalLeases.get(key);
|
|
715
|
+
if (prior) {
|
|
716
|
+
const outcome = await prior;
|
|
717
|
+
debug.log('pod.provenance', 'lease-terminal-suppressed', { workerId: command.workerId,
|
|
718
|
+
leaseId: command.leaseId, generation: command.generation, requested: 'quarantine', outcome });
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
const terminal = this.quarantineOnce(command, reason, leaseStarted).then(() => 'quarantined' as const);
|
|
722
|
+
this.terminalLeases.set(key, terminal);
|
|
723
|
+
try { await terminal; }
|
|
724
|
+
catch (error) { if (this.terminalLeases.get(key) === terminal) this.terminalLeases.delete(key); throw error; }
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
private async quarantineOnce(command: WarmWorkerCommand, reason: string, leaseStarted: boolean): Promise<void> {
|
|
363
728
|
if (!await this.transition(command, 'quarantine', this.options.memberToken, reason.slice(0, 120) || 'mismatch')) {
|
|
364
729
|
throw new Error('warm quarantine unavailable');
|
|
365
730
|
}
|
|
366
|
-
const candidate = this.
|
|
731
|
+
const candidate = this.candidates.find(row => row.spec.workerId === command.workerId);
|
|
367
732
|
if (candidate) {
|
|
368
733
|
const cleared = patchWarmLeaseMetadata(candidate.spec, command, 'quarantined', this.options.kubectl);
|
|
369
734
|
if (!cleared.ok && reason !== 'provenance-unavailable') debug.log('pod.provenance', 'lease-patch-refused', { pod: candidate.spec.name,
|
|
@@ -381,11 +746,17 @@ let configuredProvider: WarmPodProvider | null = null;
|
|
|
381
746
|
/** The trusted producer configures this once; an absent provider fails closed without creating a Pod. */
|
|
382
747
|
export function configureWarmPodProvider(provider: WarmPodProvider | null): void { configuredProvider = provider; }
|
|
383
748
|
/** Optional private producer config; a missing/unsafe file leaves the Job path untouched. */
|
|
384
|
-
export function loadWarmPodProvider(path: string): WarmPodProvider {
|
|
749
|
+
export function loadWarmPodProvider(path: string, pairingGateFile?: string): WarmPodProvider {
|
|
385
750
|
if (!isAbsolute(path) || (statSync(path).mode & 0o077) !== 0) throw new Error('warm provider config must be private');
|
|
386
751
|
const parsed: unknown = JSON.parse(readFileSync(path, 'utf8'));
|
|
387
752
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('invalid warm provider config');
|
|
388
|
-
|
|
753
|
+
const options = parsed as WarmPodProviderOptions;
|
|
754
|
+
// The production entry points (--prepare/--reconcile/--serve) bind the 1-file gate key here.
|
|
755
|
+
if (pairingGateFile !== undefined) {
|
|
756
|
+
if (!pairingGateFile.trim()) throw new Error('warm provider pairing gate file must not be empty');
|
|
757
|
+
options.pairingGateFile = pairingGateFile;
|
|
758
|
+
}
|
|
759
|
+
return new WarmPodProvider(options);
|
|
389
760
|
}
|
|
390
761
|
function currentProvider(): WarmPodProvider | null {
|
|
391
762
|
if (configuredProvider) return configuredProvider;
|
|
@@ -396,7 +767,13 @@ function currentProvider(): WarmPodProvider | null {
|
|
|
396
767
|
return configuredProvider;
|
|
397
768
|
}
|
|
398
769
|
export async function requestWarmPodReplay(input: WarmReplayInput): Promise<WarmReplayResult> {
|
|
399
|
-
|
|
770
|
+
const provider = currentProvider();
|
|
771
|
+
const result = provider ? await provider.request(input) : fallback('no-candidate');
|
|
772
|
+
debug.log('pod.warm', 'request-selection', { runId: input.runId, attemptId: input.attemptId,
|
|
773
|
+
cutSha: input.cutSha, selected: result.command !== null,
|
|
774
|
+
workerId: result.command?.workerId ?? null, status: result.transport.status,
|
|
775
|
+
reason: result.transport.reason });
|
|
776
|
+
return result;
|
|
400
777
|
}
|
|
401
778
|
export async function quarantineWarmPodReplay(command: WarmWorkerCommand, reason: string): Promise<void> {
|
|
402
779
|
const provider = currentProvider();
|
|
@@ -409,13 +786,91 @@ export async function finalizeWarmPodReplay(command: WarmWorkerCommand): Promise
|
|
|
409
786
|
await provider.finalize(command);
|
|
410
787
|
}
|
|
411
788
|
|
|
789
|
+
|
|
790
|
+
/** CLI 인자 — `--prepare <workerId> [gateFile]` · `--reconcile [gateFile]` · `--serve [gateFile]` · `--pairs`.
|
|
791
|
+
* gateFile 은 --prepare 에서만 셋째 칸, 나머지는 둘째 칸이다. `--pairs` 는 logs.db 읽기 전용(인자 없음). */
|
|
792
|
+
export function parseWarmPodProviderArgs(argv: readonly string[]): { action: '--prepare' | '--reconcile' | '--serve' | '--pairs'; workerId?: string; gateFile?: string } {
|
|
793
|
+
const usage = () => new Error('usage: --prepare <workerId> [gateFile] | --reconcile [gateFile] | --serve [gateFile]'
|
|
794
|
+
+ ' with private provider config | --pairs');
|
|
795
|
+
if (argv[0] === '--pairs') {
|
|
796
|
+
if (argv.length !== 1) throw usage();
|
|
797
|
+
return { action: '--pairs' };
|
|
798
|
+
}
|
|
799
|
+
const [action, ...rest] = argv;
|
|
800
|
+
if (action === '--prepare') {
|
|
801
|
+
const [workerId, gateFile, extra] = rest;
|
|
802
|
+
if (!workerId || extra !== undefined || (gateFile !== undefined && !gateFile.trim())) throw usage();
|
|
803
|
+
return { action, workerId, ...(gateFile !== undefined ? { gateFile } : {}) };
|
|
804
|
+
}
|
|
805
|
+
if (action === '--reconcile' || action === '--serve') {
|
|
806
|
+
const [gateFile, extra] = rest;
|
|
807
|
+
if (extra !== undefined || (gateFile !== undefined && !gateFile.trim())) throw usage();
|
|
808
|
+
return { action, ...(gateFile !== undefined ? { gateFile } : {}) };
|
|
809
|
+
}
|
|
810
|
+
throw usage();
|
|
811
|
+
}
|
|
812
|
+
|
|
412
813
|
if (import.meta.main) {
|
|
413
|
-
const
|
|
814
|
+
const { action, workerId, gateFile } = parseWarmPodProviderArgs(process.argv.slice(2));
|
|
414
815
|
const file = process.env.ELANOUS_WARM_POD_PROVIDER_CONFIG;
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
816
|
+
// The 1-file gate key this pool serves, bound into every cold prepare leg of this process.
|
|
817
|
+
const load = () => (file ? loadWarmPodProvider(file, gateFile) : null);
|
|
818
|
+
if (action === '--pairs') {
|
|
819
|
+
// Read-only: join the cold CLI legs and the warm gate legs both processes wrote to logs.db.
|
|
820
|
+
try {
|
|
821
|
+
const storeMod = await import('../../mss/logging/log-store.js');
|
|
822
|
+
const dbPath = storeMod.logsDbPath();
|
|
823
|
+
// No logs.db yet means no leg was ever measured: an empty pair list, not a failed query.
|
|
824
|
+
if (!existsSync(dbPath)) process.stdout.write(JSON.stringify({ warmReuseTimePairs: [] }) + '\n');
|
|
825
|
+
else {
|
|
826
|
+
const store = storeMod.LogStore.openReadOnly(dbPath);
|
|
827
|
+
try { process.stdout.write(JSON.stringify({ warmReuseTimePairs: readWarmReuseTimePairs(store) }) + '\n'); }
|
|
828
|
+
finally { store.close(); }
|
|
829
|
+
}
|
|
830
|
+
} catch (error) {
|
|
831
|
+
process.stdout.write(JSON.stringify({ warmReuseTimePairs: null, reason: 'logs-unavailable', detail: String(error) }) + '\n');
|
|
832
|
+
process.exitCode = 2;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
// The cold legs must reach the same logs.db the warm gate process (gate-node) writes; fail-open.
|
|
836
|
+
if (action !== '--pairs') try { await (await import('../../domains/standalone-log-sink.js')).registerStandaloneLogSink('pod-warm'); }
|
|
837
|
+
catch { /* the file trail stays the source of truth */ }
|
|
838
|
+
// This process's own legs, echoed for the caller; the pair itself is read with --pairs.
|
|
839
|
+
const emitPairs = (provider: WarmPodProvider) => {
|
|
840
|
+
const legs = provider.warmReuseTimeLegs();
|
|
841
|
+
if (legs.length) process.stdout.write(JSON.stringify({ warmReuseTimeLegs: legs }) + '\n');
|
|
842
|
+
};
|
|
843
|
+
if (action === '--serve') {
|
|
844
|
+
// Reloading the private config lets the owner rotate a quarantined slot to a new worker ID.
|
|
845
|
+
// The old ID remains fenced in the ledger; this loop never deletes a Pod or rewrites the ledger.
|
|
846
|
+
while (true) {
|
|
847
|
+
try {
|
|
848
|
+
const provider = load();
|
|
849
|
+
const result = provider ? await provider.reconcilePool() : null;
|
|
850
|
+
process.stdout.write(JSON.stringify(result ?? { target: 0, ready: 0, failed: ['config-unavailable'] }) + '\n');
|
|
851
|
+
if (provider) emitPairs(provider);
|
|
852
|
+
} catch {
|
|
853
|
+
debug.log('pod.warm', 'pool-reconcile-unavailable', { reason: 'config-unavailable' }, { level: 'warn' });
|
|
854
|
+
process.stdout.write(JSON.stringify({ target: 0, ready: 0, failed: ['config-unavailable'] }) + '\n');
|
|
855
|
+
}
|
|
856
|
+
await Bun.sleep(60_000);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
if (action === '--reconcile') {
|
|
860
|
+
let result: WarmPoolReconcileResult | null = null;
|
|
861
|
+
let provider: WarmPodProvider | null = null;
|
|
862
|
+
try { provider = load(); result = provider ? await provider.reconcilePool() : null; }
|
|
863
|
+
catch { /* An invalid private config is a failed pass, never a Pod mutation. */ }
|
|
864
|
+
process.stdout.write(JSON.stringify(result ?? { target: 0, ready: 0, failed: ['config-unavailable'] }) + '\n');
|
|
865
|
+
if (provider) emitPairs(provider);
|
|
866
|
+
if (!result || result.ready !== result.target) process.exitCode = 2;
|
|
867
|
+
} else if (action === '--prepare') {
|
|
868
|
+
let prepared = false;
|
|
869
|
+
let provider: WarmPodProvider | null = null;
|
|
870
|
+
try { provider = load(); prepared = provider ? await provider.prepareWorker(workerId!) : false; }
|
|
871
|
+
catch { /* An invalid private config is a failed preparation, never a Pod mutation. */ }
|
|
872
|
+
process.stdout.write(JSON.stringify({ workerId, prepared }) + '\n');
|
|
873
|
+
if (provider) emitPairs(provider);
|
|
874
|
+
if (!prepared) process.exitCode = 2;
|
|
875
|
+
}
|
|
421
876
|
}
|