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
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
// The saved version-release output is re-pointed at the new branch tip (fast-forward only, state backed up first), so
|
|
3
3
|
// every later node reads the repaired commit without anyone hand-editing the run state.
|
|
4
4
|
import { spawnSync } from 'node:child_process';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { createHash } from 'node:crypto';
|
|
6
|
+
import { accessSync, closeSync, constants, copyFileSync, existsSync, fstatSync, openSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { join, resolve } from 'node:path';
|
|
7
8
|
import { debug } from '../../src/debug/log.js';
|
|
8
9
|
import { effectiveInstanceRoot, releaseLedgerRoot } from '../../src/instance/resolve.js';
|
|
9
|
-
import { lastJsonObject, runGraph, verifyReleaseCommandRoot, type GraphRunState } from '../../src/graph-runner/runner.js';
|
|
10
|
+
import { lastJsonObject, runGraph, verifyReleaseCommandRoot, verifyReleaseRecipeSnapshot, type GraphRunState } from '../../src/graph-runner/runner.js';
|
|
10
11
|
import { withOpSeatFlock } from '../../src/steward/resync-tick.js';
|
|
11
12
|
import { releaseRunUniverse } from './release-universe.js';
|
|
12
13
|
import { isReleaseVersion } from './release-version.js';
|
|
@@ -14,6 +15,8 @@ import { changedFilesBetween, clearPartialPlan, planPartialRegate, planStalledSh
|
|
|
14
15
|
import { gitClosureTree, testsReachingChanged, TRACEABLE_RELEASE_JSON, type ClosureTree } from './gate-closure.js';
|
|
15
16
|
import { releaseResumeAcceptedRegressions, releaseResumeGateExecutor, releaseResumeGatePodPool, releaseResumeWaiver, type AcceptedRegression } from './resume.js';
|
|
16
17
|
import { reserveReleaseResumeAttempt } from './resume-cap.js';
|
|
18
|
+
import { releaseLoopConfig } from './unattended-release.js';
|
|
19
|
+
import { trafficOptionsFromConfig } from './traffic-transition.js';
|
|
17
20
|
|
|
18
21
|
const GRAPH = join(import.meta.dir, '../../graphs/release/release-loop.yaml');
|
|
19
22
|
|
|
@@ -36,6 +39,8 @@ export interface ResumeReleaseDeps {
|
|
|
36
39
|
closureTree?: (commit: string) => ClosureTree;
|
|
37
40
|
/** Source observation before the first resumed node. */
|
|
38
41
|
onSource?: (line: string) => void;
|
|
42
|
+
/** Test seam; production resolves this from ELANOUS_RELEASE_TRAFFIC_FILE / release.loop.trafficFile. */
|
|
43
|
+
trafficFile?: string;
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
export interface ResumeReleaseOptions {
|
|
@@ -47,7 +52,7 @@ export interface ResumeReleaseOptions {
|
|
|
47
52
|
export interface ResumeReleasePlan {
|
|
48
53
|
runId: string; version: string; fromNode: string; commandRoot: string; tip: TipRefresh;
|
|
49
54
|
partial: PartialOutcome; gatePlan: { mode: 'not-run' | 'partial' | 'full'; reason?: string };
|
|
50
|
-
nextAttempt: number;
|
|
55
|
+
nextAttempt: number; priorCountKnown: boolean; preflight: 'passed';
|
|
51
56
|
}
|
|
52
57
|
|
|
53
58
|
/** CLI entry files: a production file that only names one (help text, a goal's command line) does not run it. */
|
|
@@ -79,12 +84,12 @@ function safeRunId(runId: string): string {
|
|
|
79
84
|
return runId;
|
|
80
85
|
}
|
|
81
86
|
|
|
82
|
-
/**
|
|
87
|
+
/** Lower-level maintenance primitive; the resume entrypoints run verifyResumePreflight before calling it. */
|
|
83
88
|
export function refreshReleaseBranchTip(runId: string, deps: ResumeReleaseDeps = {}, from?: string, waiverCommit?: string): TipRefresh {
|
|
84
89
|
return refreshReleaseBranchTipInternal(runId, deps, from, waiverCommit, false);
|
|
85
90
|
}
|
|
86
91
|
|
|
87
|
-
function refreshReleaseBranchTipInternal(runId: string, deps: ResumeReleaseDeps, from: string | undefined, waiverCommit: string | undefined, dryRun: boolean): TipRefresh {
|
|
92
|
+
function refreshReleaseBranchTipInternal(runId: string, deps: ResumeReleaseDeps, from: string | undefined, waiverCommit: string | undefined, dryRun: boolean, expectedTip?: string): TipRefresh {
|
|
88
93
|
const root = deps.root ?? effectiveInstanceRoot();
|
|
89
94
|
const statePath = join(root, 'graph-runs', 'release-loop', `${safeRunId(runId)}.json`);
|
|
90
95
|
const state = JSON.parse(readFileSync(statePath, 'utf8')) as GraphRunState;
|
|
@@ -108,6 +113,7 @@ function refreshReleaseBranchTipInternal(runId: string, deps: ResumeReleaseDeps,
|
|
|
108
113
|
const git = deps.git ?? gitRunner(deps.repo ?? process.cwd());
|
|
109
114
|
const advertised = git(['ls-remote', '--heads', 'origin', `refs/heads/${branch}`]).split(/\s+/)[0] ?? '';
|
|
110
115
|
if (!/^[0-9a-f]{40}$/i.test(advertised)) throw new Error(`${branch} is not on origin — this run was not cut as a release branch`);
|
|
116
|
+
if (expectedTip && advertised.toLowerCase() !== expectedTip.toLowerCase()) throw new Error(`${branch} moved during pin preflight (${expectedTip} → ${advertised}) — retry resume`);
|
|
111
117
|
let to: string;
|
|
112
118
|
if (dryRun) {
|
|
113
119
|
// Use the advertised object id directly: a preflight must not create/update local remote-tracking refs.
|
|
@@ -126,7 +132,7 @@ function refreshReleaseBranchTipInternal(runId: string, deps: ResumeReleaseDeps,
|
|
|
126
132
|
}
|
|
127
133
|
// A repair only appends (`cut-branch --append`): anything but a fast-forward is a different release.
|
|
128
134
|
if (!(deps.isAncestor ?? ancestorRunner(deps.repo ?? process.cwd()))(recorded, to)) {
|
|
129
|
-
throw new Error(`${branch} tip ${to} does not descend from the recorded release commit ${recorded} — refusing to resume`);
|
|
135
|
+
throw new Error(`${branch} tip ${to} does not descend from the recorded release commit ${recorded} — refusing to resume; remedy: inspect both commits and create a new release run from the intended pinned tip`);
|
|
130
136
|
}
|
|
131
137
|
// A new tip invalidates everything built from the old one (gate verdict, prepared archive, …): restart no later
|
|
132
138
|
// than the first node that consumed the commit, so each of them runs again on the new tip.
|
|
@@ -152,6 +158,130 @@ function refreshReleaseBranchTipInternal(runId: string, deps: ResumeReleaseDeps,
|
|
|
152
158
|
return { version, branch, from: recorded, to, changed: true, statePath, backup };
|
|
153
159
|
}
|
|
154
160
|
|
|
161
|
+
/** Check the saved graph and recipes before fetch, backup, or run-ledger writes. */
|
|
162
|
+
function verifyResumeRecipePin(commandRoot: string, snapshot: GraphRunState['graphSnapshot'], version: string): void {
|
|
163
|
+
try { verifyReleaseRecipeSnapshot(commandRoot, snapshot); }
|
|
164
|
+
catch (error) {
|
|
165
|
+
if (!snapshot) throw error;
|
|
166
|
+
const mismatches: string[] = [];
|
|
167
|
+
for (const [name, savedSha] of [
|
|
168
|
+
['release-loop.yaml', snapshot.graphSha], ['recipes.yaml', snapshot.recipesSha],
|
|
169
|
+
] as const) {
|
|
170
|
+
const file = join(commandRoot, 'graphs', 'release', name);
|
|
171
|
+
let currentSha: string;
|
|
172
|
+
try { currentSha = createHash('sha256').update(readFileSync(file)).digest('hex'); }
|
|
173
|
+
catch { continue; }
|
|
174
|
+
if (currentSha !== savedSha) mismatches.push(`${file} saved=${savedSha} current=${currentSha}`);
|
|
175
|
+
}
|
|
176
|
+
if (!mismatches.length) throw error;
|
|
177
|
+
throw new Error(`release recipe source differs from saved pin: ${mismatches.join('; ')}\nremedy: start a new pinned run with eln release run --version ${version}`, { cause: error });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Common read-only gate for dry-run and actual resume. Every rejection includes an operator action. */
|
|
182
|
+
function verifyResumePreflight(run: GraphRunState, opts: ResumeReleaseOptions, deps: ResumeReleaseDeps,
|
|
183
|
+
version: string, recordedCommit: string, currentTip: string, commandRoot: string): void {
|
|
184
|
+
let trafficFile = deps.trafficFile;
|
|
185
|
+
if (trafficFile === undefined) {
|
|
186
|
+
try { trafficFile = trafficOptionsFromConfig(process.env, releaseLoopConfig()).trafficFile; }
|
|
187
|
+
catch (error) { throw new Error(`cannot read release traffic configuration — remedy: repair release.loop.trafficFile in the active config before resuming`, { cause: error }); }
|
|
188
|
+
}
|
|
189
|
+
if (!trafficFile) throw new Error('release.loop.trafficFile is not configured — remedy: set release.loop.trafficFile or ELANOUS_RELEASE_TRAFFIC_FILE before resuming');
|
|
190
|
+
let fd: number | undefined;
|
|
191
|
+
try {
|
|
192
|
+
const path = resolve(trafficFile);
|
|
193
|
+
const info = statSync(path);
|
|
194
|
+
if (!info.isFile() || (info.mode & 0o222) === 0) throw new Error('not a writable regular file');
|
|
195
|
+
fd = openSync(path, constants.O_WRONLY);
|
|
196
|
+
if (!fstatSync(fd).isFile()) throw new Error('not a regular file');
|
|
197
|
+
accessSync(path, constants.R_OK | constants.W_OK);
|
|
198
|
+
} catch (error) {
|
|
199
|
+
throw new Error(`release traffic file is missing or not writable: ${trafficFile} (${error instanceof Error ? error.message : String(error)}) — remedy: create a regular writable traffic file and set release.loop.trafficFile`, { cause: error });
|
|
200
|
+
} finally { if (fd !== undefined) closeSync(fd); }
|
|
201
|
+
|
|
202
|
+
for (const path of [join(commandRoot, 'node_modules'), join(commandRoot, 'apps', 'pwa', 'node_modules')]) {
|
|
203
|
+
try { if (!statSync(path).isDirectory()) throw new Error('not a directory'); }
|
|
204
|
+
catch (error) { throw new Error(`release resume dependencies missing: ${path} — remedy: run bun install --frozen-lockfile at repository root and apps/pwa`, { cause: error }); }
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const nodeOutput = (nodeId: string): Record<string, unknown> | undefined => {
|
|
208
|
+
const node = run.nodes.find((entry) => entry.nodeId === nodeId);
|
|
209
|
+
if (!node?.ok || typeof node.output !== 'string') return undefined;
|
|
210
|
+
try { return lastJsonObject(node.output) as Record<string, unknown> | undefined; } catch { return undefined; }
|
|
211
|
+
};
|
|
212
|
+
const recordedNodeOutput = (nodeId: string): Record<string, unknown> | undefined => {
|
|
213
|
+
const node = run.nodes.find((entry) => entry.nodeId === nodeId);
|
|
214
|
+
if (typeof node?.output !== 'string') return undefined;
|
|
215
|
+
try { return lastJsonObject(node.output) as Record<string, unknown> | undefined; } catch { return undefined; }
|
|
216
|
+
};
|
|
217
|
+
const roots = [...new Set([deps.root ?? effectiveInstanceRoot(), deps.ledgerRoot ?? releaseLedgerRoot()])];
|
|
218
|
+
for (const root of roots) {
|
|
219
|
+
const prepared = join(root, 'release', version, 'prepared');
|
|
220
|
+
if (existsSync(prepared)) {
|
|
221
|
+
const record = nodeOutput('prepare');
|
|
222
|
+
const output = record && typeof record.out === 'string' ? record : undefined;
|
|
223
|
+
const manifestPath = join(prepared, 'release.json');
|
|
224
|
+
let manifest: { version?: unknown; sourceCommit?: unknown } | undefined;
|
|
225
|
+
try { manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) as typeof manifest; } catch { /* fail closed below */ }
|
|
226
|
+
const ownerPath = join(prepared, '.release-node-owner.json');
|
|
227
|
+
let owner: { runId?: unknown; commit?: unknown } | undefined;
|
|
228
|
+
let ownerReadable = !existsSync(ownerPath);
|
|
229
|
+
try { owner = JSON.parse(readFileSync(ownerPath, 'utf8')) as typeof owner; ownerReadable = true; } catch { /* old artifacts can be owned by the run ledger */ }
|
|
230
|
+
const ledgerOwns = output?.out === prepared && output.commit === currentTip && output.outcome === 'ok';
|
|
231
|
+
const receiptOwns = owner ? owner.runId === opts.runId && owner.commit === currentTip : ownerReadable && ledgerOwns;
|
|
232
|
+
if (!ledgerOwns || manifest?.version !== version || manifest.sourceCommit !== currentTip || !receiptOwns) {
|
|
233
|
+
throw new Error(`prepared artifact is outside the current release run ledger: ${prepared} — remedy: preserve it, compare release.json and .release-node-owner.json with run ${opts.runId}, then resolve ownership before resume`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const receiptPath = join(root, 'release', version, 'docs-branch-owner.json');
|
|
238
|
+
const docsBranch = `release-docs/${version}`;
|
|
239
|
+
let receipt: { owner?: { runId?: unknown; attempt?: unknown }; branch?: unknown; worktree?: unknown; outcome?: unknown; head?: unknown } | undefined;
|
|
240
|
+
try { receipt = JSON.parse(readFileSync(receiptPath, 'utf8')) as typeof receipt; } catch { /* no/invalid receipt is rejected if an artifact exists */ }
|
|
241
|
+
const docsRecord = recordedNodeOutput('docs');
|
|
242
|
+
const worktree = typeof docsRecord?.worktree === 'string' ? docsRecord.worktree : undefined;
|
|
243
|
+
let localBranch: string | undefined;
|
|
244
|
+
try { localBranch = (deps.git ?? gitRunner(deps.repo ?? process.cwd()))(['show-ref', '--verify', '--hash', `refs/heads/${docsBranch}`]).trim(); } catch { /* absent */ }
|
|
245
|
+
let docsWorktrees: string[] = [];
|
|
246
|
+
try {
|
|
247
|
+
const listing = (deps.git ?? gitRunner(deps.repo ?? process.cwd()))(['worktree', 'list', '--porcelain']);
|
|
248
|
+
docsWorktrees = listing.split(/\n\n+/).filter(Boolean).flatMap((entry) => {
|
|
249
|
+
const branch = /^branch (.+)$/m.exec(entry)?.[1];
|
|
250
|
+
const path = /^worktree (.+)$/m.exec(entry)?.[1];
|
|
251
|
+
return branch === `refs/heads/${docsBranch}` && path ? [path] : [];
|
|
252
|
+
});
|
|
253
|
+
} catch (error) {
|
|
254
|
+
throw new Error(`cannot inspect release worktrees for ${docsBranch} — remedy: restore read-only git access and retry resume`, { cause: error });
|
|
255
|
+
}
|
|
256
|
+
let remoteHead: string | undefined;
|
|
257
|
+
try {
|
|
258
|
+
const response = (deps.git ?? gitRunner(deps.repo ?? process.cwd()))(['ls-remote', '--heads', 'origin', `refs/heads/${docsBranch}`]);
|
|
259
|
+
const line = response.split(/\r?\n/).find((candidate) => candidate.trim().endsWith(`refs/heads/${docsBranch}`));
|
|
260
|
+
remoteHead = line?.trim().split(/\s+/)[0];
|
|
261
|
+
} catch (error) {
|
|
262
|
+
throw new Error(`cannot inspect docs branch ${docsBranch} — remedy: restore read-only git access and retry resume`, { cause: error });
|
|
263
|
+
}
|
|
264
|
+
const hasDocsArtifact = existsSync(receiptPath) || !!localBranch || !!remoteHead || docsWorktrees.length > 0;
|
|
265
|
+
const outputOwner = docsRecord?.artifactOwner as { runId?: unknown; attempt?: unknown } | undefined;
|
|
266
|
+
const receiptMatches = receipt?.branch === docsBranch && receipt.owner?.runId === opts.runId
|
|
267
|
+
&& receipt.owner.attempt === outputOwner?.attempt && outputOwner?.runId === opts.runId
|
|
268
|
+
&& typeof receipt.worktree === 'string' && worktree === receipt.worktree && docsWorktrees.includes(receipt.worktree)
|
|
269
|
+
&& existsSync(receipt.worktree);
|
|
270
|
+
if (hasDocsArtifact && !receiptMatches) {
|
|
271
|
+
throw new Error(`docs branch/worktree is outside the current release run ledger: ${docsBranch} — remedy: compare ${receiptPath} with the saved docs node output and resolve ownership before resume`);
|
|
272
|
+
}
|
|
273
|
+
if (receipt?.outcome === 'pushed' && (!/^[0-9a-f]{40}$/i.test(String(receipt.head ?? ''))
|
|
274
|
+
|| remoteHead?.toLowerCase() !== String(receipt.head).toLowerCase())) {
|
|
275
|
+
throw new Error(`docs branch tip differs from the saved ownership receipt: ${docsBranch} — remedy: preserve the branch and worktree, compare the remote tip with ${receiptPath} and the saved docs node output, then resolve ownership before resume`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const recorded = nodeOutput('version-release');
|
|
280
|
+
if (recorded?.branch !== `release/${version}` || recorded.commit !== recordedCommit || !/^[0-9a-f]{40}$/i.test(currentTip)) {
|
|
281
|
+
throw new Error(`release branch tip does not match the saved version-release ledger for ${version} — remedy: inspect the branch and saved run ${opts.runId}, then resume from a node before gate only after resolving the mismatch`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
155
285
|
/** GATE-PARTIAL outcome of a resume — applied (plan written) or the reason it fell back to a full gate. `auto` marks the
|
|
156
286
|
* default plan of `--from gate` (GATE-REUSE-BY-DIFF); `closure` is set when code changes were followed. */
|
|
157
287
|
export type PartialOutcome = { requested: false }
|
|
@@ -318,7 +448,7 @@ async function planResumeReleaseRunLocked(opts: ResumeReleaseOptions, deps: Resu
|
|
|
318
448
|
const waiver = original && (opts.waive !== undefined || opts.reason !== undefined) ? releaseResumeWaiver(original, opts) : undefined;
|
|
319
449
|
if (original && opts.acceptedRegressions !== undefined && !waiver)
|
|
320
450
|
releaseResumeAcceptedRegressions(original, opts.acceptedRegressions, opts.from);
|
|
321
|
-
const attempt = reserveReleaseResumeAttempt(savedRun, deps.root!, { now: deps.now, checkOnly: true
|
|
451
|
+
const attempt = reserveReleaseResumeAttempt(savedRun, deps.root!, { now: deps.now, checkOnly: true });
|
|
322
452
|
let waiverCommit: string | undefined;
|
|
323
453
|
if (waiver) {
|
|
324
454
|
const output = lastJsonObject(savedRun.nodes.find((node) => node.nodeId === 'version-release')?.output);
|
|
@@ -331,7 +461,9 @@ async function planResumeReleaseRunLocked(opts: ResumeReleaseOptions, deps: Resu
|
|
|
331
461
|
waiverCommit = recorded;
|
|
332
462
|
}
|
|
333
463
|
const tip = refreshReleaseBranchTipInternal(opts.runId, deps, opts.from, waiverCommit, true);
|
|
464
|
+
verifyResumeRecipePin(opts.commandRoot ?? process.cwd(), savedRun.graphSnapshot, tip.version);
|
|
334
465
|
const commandRoot = verifyReleaseCommandRoot(opts.commandRoot ?? process.cwd(), tip.to, deps.git);
|
|
466
|
+
verifyResumePreflight(savedRun, opts, deps, tip.version, tip.from, tip.to, commandRoot);
|
|
335
467
|
const partial = preparePartial(tip, opts, deps, true);
|
|
336
468
|
const fromIndex = savedRun.path.indexOf(opts.from);
|
|
337
469
|
const gateIndex = savedRun.path.indexOf('gate');
|
|
@@ -340,7 +472,7 @@ async function planResumeReleaseRunLocked(opts: ResumeReleaseOptions, deps: Resu
|
|
|
340
472
|
: partial.requested && partial.applied ? { mode: 'partial' as const }
|
|
341
473
|
: { mode: 'full' as const, reason: partial.requested ? partial.reason : opts.partial === false ? 'partial recheck disabled' : 'no partial plan applies' };
|
|
342
474
|
return { runId: opts.runId, version: tip.version, fromNode: opts.from, commandRoot, tip, partial,
|
|
343
|
-
gatePlan, nextAttempt: attempt.attempt,
|
|
475
|
+
gatePlan, nextAttempt: attempt.attempt, priorCountKnown: attempt.priorCountKnown, preflight: 'passed' };
|
|
344
476
|
}
|
|
345
477
|
|
|
346
478
|
async function resumeReleaseRunLocked(opts: ResumeReleaseOptions, deps: ResumeReleaseDeps): Promise<{ tip: TipRefresh; partial: PartialOutcome; state: GraphRunState }> {
|
|
@@ -368,7 +500,7 @@ async function resumeReleaseRunLocked(opts: ResumeReleaseOptions, deps: ResumeRe
|
|
|
368
500
|
const acceptedRegressions = original && opts.acceptedRegressions !== undefined && !waiver
|
|
369
501
|
? releaseResumeAcceptedRegressions(original, opts.acceptedRegressions, opts.from) : undefined;
|
|
370
502
|
if (savedRun.graphId !== 'release-loop' || savedRun.runId !== opts.runId) throw new Error(`run identity mismatch: ${statePath}`);
|
|
371
|
-
// Validate request arguments before the
|
|
503
|
+
// Validate request arguments before the tip refresh; the count is observational.
|
|
372
504
|
reserveReleaseResumeAttempt(savedRun, deps.root!, { now: deps.now, checkOnly: true });
|
|
373
505
|
// A waived verdict may reuse its original commit. A moved tip still requires a full re-gate.
|
|
374
506
|
if (waiver) {
|
|
@@ -383,13 +515,17 @@ async function resumeReleaseRunLocked(opts: ResumeReleaseOptions, deps: ResumeRe
|
|
|
383
515
|
if (advertised?.toLowerCase() !== recorded.toLowerCase()) throw new Error('release branch moved — re-gate the new tip before waiving a node');
|
|
384
516
|
waiverCommit = recorded;
|
|
385
517
|
}
|
|
386
|
-
//
|
|
387
|
-
const
|
|
388
|
-
|
|
518
|
+
// The read-only tip and recipe pin preflight must pass before fetch or a saved-run rewrite.
|
|
519
|
+
const previewTip = refreshReleaseBranchTipInternal(opts.runId, deps, opts.from, waiverCommit, true);
|
|
520
|
+
verifyResumeRecipePin(opts.commandRoot ?? process.cwd(), savedRun.graphSnapshot, previewTip.version);
|
|
521
|
+
const commandRoot = verifyReleaseCommandRoot(opts.commandRoot ?? process.cwd(), previewTip.to, deps.git);
|
|
522
|
+
verifyResumePreflight(savedRun, opts, deps, previewTip.version, previewTip.from, previewTip.to, commandRoot);
|
|
523
|
+
const tip = refreshReleaseBranchTipInternal(opts.runId, deps, opts.from, waiverCommit, false, previewTip.to);
|
|
389
524
|
const partial = preparePartial(tip, opts, deps);
|
|
390
|
-
// A failed preflight does not consume
|
|
525
|
+
// A failed preflight does not consume an observed release resume attempt.
|
|
391
526
|
const attempt = reserveReleaseResumeAttempt(savedRun, deps.root!, { now: deps.now });
|
|
392
|
-
debug.log('release-loop.resume', 'attempt', { runId: opts.runId, attempt: attempt.attempt,
|
|
527
|
+
debug.log('release-loop.resume', 'attempt', { runId: opts.runId, attempt: attempt.attempt,
|
|
528
|
+
priorCountKnown: attempt.priorCountKnown, ledger: attempt.path });
|
|
393
529
|
// Resume the graph at the path it was started from (its snapshot is used); only tests inject another path.
|
|
394
530
|
const graphPath = deps.graphPath ?? savedGraphPath(tip.statePath) ?? GRAPH;
|
|
395
531
|
const savedSource = JSON.parse(readFileSync(tip.statePath, 'utf8')) as GraphRunState;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Resume tests need the same writable traffic-file prerequisite as the installed release configuration.
|
|
2
|
+
// This module is imported only by test files; the gate process owns and discards this environment.
|
|
3
|
+
import { rmSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
const trafficFile = join(tmpdir(), `elanous-resume-test-traffic-${process.pid}.json`);
|
|
8
|
+
writeFileSync(trafficFile, '{}\n');
|
|
9
|
+
process.env.ELANOUS_RELEASE_TRAFFIC_FILE = trafficFile;
|
|
10
|
+
process.once('exit', () => rmSync(trafficFile, { force: true }));
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { debug } from '../../src/debug/log.js';
|
|
4
|
+
import { SPAWN_SYNC_MAX_BUFFER } from '../../src/util/spawn-sync-output.js';
|
|
5
|
+
import { releaseLoopConfig } from './unattended-release.js';
|
|
6
|
+
import { errorResult, finishNode, readGraphContext } from './node-verdict.js';
|
|
7
|
+
import { applyReleaseTrafficEvent, trafficOptionsFromConfig, type ReleaseTrafficEvent, type TrafficTransitionOptions } from './traffic-transition.js';
|
|
8
|
+
|
|
9
|
+
const EVENTS: Record<string, ReleaseTrafficEvent> = {
|
|
10
|
+
'traffic-gate-passed': 'gate-passed',
|
|
11
|
+
'traffic-verify-complete': 'verify-complete',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export interface TrafficNodeDeps {
|
|
15
|
+
options?: TrafficTransitionOptions;
|
|
16
|
+
report?: (body: string) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function reportFailure(body: string): void {
|
|
20
|
+
const result = spawnSync('eln', ['msg', 'post', '--from', 'MK', '--to', 'OP', '--kind', 'report', '--body', body], { encoding: 'utf8', timeout: 10_000, maxBuffer: SPAWN_SYNC_MAX_BUFFER });
|
|
21
|
+
if (result.status !== 0 || result.error) process.stderr.write(`traffic OP mail failed: ${result.stderr || result.error || result.status}\n`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Traffic transitions are warning-only for the release graph, but their node result remains truthful. */
|
|
25
|
+
export async function runTrafficNode(node = process.argv[2], env: NodeJS.ProcessEnv = process.env, deps: TrafficNodeDeps = {}): Promise<number> {
|
|
26
|
+
let version = '';
|
|
27
|
+
let runId = '';
|
|
28
|
+
try {
|
|
29
|
+
const context = readGraphContext(env);
|
|
30
|
+
version = context.input.version;
|
|
31
|
+
runId = context.runId ?? '';
|
|
32
|
+
const event = EVENTS[node ?? ''];
|
|
33
|
+
if (!event || context.nodeId !== node) throw new Error(`traffic node context mismatch: ${node ?? 'missing'}`);
|
|
34
|
+
const options = deps.options ?? trafficOptionsFromConfig(env, releaseLoopConfig());
|
|
35
|
+
if (!options.trafficFile) {
|
|
36
|
+
const summary = `traffic-file-unset: ${event} skipped because trafficFile is not configured`;
|
|
37
|
+
debug.log('release-loop.traffic', 'traffic-file-unset', { runId, version, event, node, reason: summary });
|
|
38
|
+
(deps.report ?? reportFailure)(`RELEASE-POSTPUB-TRAFFIC ${version} ${runId || 'unknown'} ${node} ${summary}. Release traffic was not changed.`);
|
|
39
|
+
return finishNode(node!, version, { outcome: 'fail', verdict: 'fail', summary });
|
|
40
|
+
}
|
|
41
|
+
const result = await applyReleaseTrafficEvent(context, event, options);
|
|
42
|
+
if (!result.applied) {
|
|
43
|
+
const consequence = event === 'verify-complete'
|
|
44
|
+
? '릴리스 검증은 성공했으나 트래픽 복귀가 적용되지 않았습니다.'
|
|
45
|
+
: 'gate는 성공했으나 트래픽 감속이 적용되지 않았습니다.';
|
|
46
|
+
(deps.report ?? reportFailure)(`RELEASE-POSTPUB-TRAFFIC ${version} ${runId || 'unknown'} ${node} 무시 — ${result.reason ?? 'traffic state unchanged'}. ${consequence}`);
|
|
47
|
+
}
|
|
48
|
+
return finishNode(node!, version, { outcome: 'ok', verdict: 'pass', summary: `${event} ${result.applied ? 'applied' : result.reason ?? 'ignored'}` });
|
|
49
|
+
} catch (error) {
|
|
50
|
+
const detail = errorResult(error).summary;
|
|
51
|
+
const isLockTimeout = detail.startsWith('traffic lock timeout:');
|
|
52
|
+
const failure = isLockTimeout ? `traffic-lock-timeout · stale 의심 경로 ${detail.slice('traffic lock timeout:'.length).trim()}` : detail;
|
|
53
|
+
debug.log('release-loop.traffic', 'non-blocking-failure', { node: node ?? 'missing', version, reason: detail });
|
|
54
|
+
(deps.report ?? reportFailure)(`RELEASE-POSTPUB-TRAFFIC ${version || 'unknown'} ${runId || 'unknown'} ${node ?? 'unknown'} 실패 — ${failure}. 릴리스 판정은 계속 진행한다.`);
|
|
55
|
+
return finishNode(node ?? 'traffic', version, { outcome: 'fail', verdict: 'fail', summary: `traffic transition failed; release graph continues with warning: ${failure}` });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (import.meta.main) process.exitCode = await runTrafficNode();
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { debug } from '../../src/debug/log.js';
|
|
4
|
+
import type { GraphContext } from './node-verdict.js';
|
|
5
|
+
import type { ReleaseLoopConfig } from './unattended-release.js';
|
|
6
|
+
|
|
7
|
+
export type ReleaseTrafficEvent = 'gate-passed' | 'verify-complete';
|
|
8
|
+
|
|
9
|
+
export interface TrafficFile {
|
|
10
|
+
global?: string;
|
|
11
|
+
seats: Record<string, Record<string, unknown>>;
|
|
12
|
+
releaseLifecycle?: { runId: string; version: string; stage: 1 | 2; event: ReleaseTrafficEvent; startedAt?: string; updatedAt: string };
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type ReleaseTrafficConfig = ReleaseLoopConfig & {
|
|
17
|
+
trafficFile?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const SEATS = ['OP', 'TC', 'MK', 'UX'] as const;
|
|
21
|
+
const STAGE: Record<ReleaseTrafficEvent, 1 | 2> = { 'gate-passed': 1, 'verify-complete': 2 };
|
|
22
|
+
const TRAFFIC_PER_CYCLE = { half: 3, full: 6 } as const;
|
|
23
|
+
|
|
24
|
+
export interface TrafficTransitionOptions {
|
|
25
|
+
trafficFile?: string;
|
|
26
|
+
lockTimeoutMs?: number;
|
|
27
|
+
now?: () => Date;
|
|
28
|
+
log?: (event: string, fields: Record<string, unknown>) => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function waitSync(ms: number): void {
|
|
32
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function acquireTrafficLock(path: string, timeoutMs = 10_000): () => void {
|
|
36
|
+
const lock = `${path}.lock.d`;
|
|
37
|
+
const deadline = Date.now() + timeoutMs;
|
|
38
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
39
|
+
try {
|
|
40
|
+
mkdirSync(lock);
|
|
41
|
+
try { writeFileSync(`${lock}/pid`, `${process.pid}\n`, { flag: 'wx', mode: 0o600 }); }
|
|
42
|
+
catch (error) { rmSync(lock, { recursive: true, force: true }); throw error; }
|
|
43
|
+
return () => rmSync(lock, { recursive: true, force: true });
|
|
44
|
+
} catch (error) {
|
|
45
|
+
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
|
|
46
|
+
try {
|
|
47
|
+
// A directory cannot be conditionally renamed by identity. Reclaiming after
|
|
48
|
+
// checking its PID can therefore move a replacement owner's live lock.
|
|
49
|
+
// Fail closed; OP can inspect and remove a stale lock after the warning mail.
|
|
50
|
+
readFileSync(`${lock}/pid`, 'utf8');
|
|
51
|
+
statSync(lock);
|
|
52
|
+
} catch { /* Unknown or concurrently changing locks are left intact. */ }
|
|
53
|
+
if (Date.now() >= deadline) throw new Error(`traffic lock timeout: ${lock}`);
|
|
54
|
+
waitSync(100);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function readTrafficFile(path: string): TrafficFile {
|
|
60
|
+
const parsed: unknown = JSON.parse(readFileSync(path, 'utf8'));
|
|
61
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('traffic file must contain a JSON object');
|
|
62
|
+
const file = parsed as TrafficFile;
|
|
63
|
+
if (!file.seats || typeof file.seats !== 'object' || Array.isArray(file.seats)
|
|
64
|
+
|| !SEATS.every((seat) => file.seats[seat] && typeof file.seats[seat] === 'object' && !Array.isArray(file.seats[seat]))) {
|
|
65
|
+
throw new Error('traffic seats object missing');
|
|
66
|
+
}
|
|
67
|
+
return file;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function atomicWriteTrafficFile(path: string, file: TrafficFile): void {
|
|
71
|
+
const temp = `${path}.tmp.${process.pid}.${Date.now()}`;
|
|
72
|
+
const mode = statSync(path).mode & 0o777;
|
|
73
|
+
try {
|
|
74
|
+
writeFileSync(temp, `${JSON.stringify(file, null, 2)}\n`, { flag: 'wx', mode });
|
|
75
|
+
renameSync(temp, path);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
rmSync(temp, { force: true });
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function compareReleaseVersions(left: string, right: string): number {
|
|
83
|
+
const parse = (value: string) => /^(\d+)\.(\d+)\.(\d+)(?:-(alpha|beta|rc)\.(\d+))?$/.exec(value);
|
|
84
|
+
const a = parse(left), b = parse(right);
|
|
85
|
+
if (!a || !b) return left.localeCompare(right);
|
|
86
|
+
for (const index of [1, 2, 3]) {
|
|
87
|
+
const delta = Number(a[index]) - Number(b[index]);
|
|
88
|
+
if (delta) return Math.sign(delta);
|
|
89
|
+
}
|
|
90
|
+
if (!a[4] || !b[4]) return a[4] ? -1 : b[4] ? 1 : 0;
|
|
91
|
+
const rank = { alpha: 0, beta: 1, rc: 2 } as const;
|
|
92
|
+
const kind = rank[a[4] as keyof typeof rank] - rank[b[4] as keyof typeof rank];
|
|
93
|
+
return kind || Math.sign(Number(a[5]) - Number(b[5]));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function isLaterRun(current: string | undefined, previous: string | undefined): boolean {
|
|
97
|
+
return Boolean(current && previous && current > previous);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Apply one monotonic release graph event while preserving unrelated traffic policy fields. */
|
|
101
|
+
export async function applyReleaseTrafficEvent(
|
|
102
|
+
context: GraphContext,
|
|
103
|
+
event: ReleaseTrafficEvent,
|
|
104
|
+
options: TrafficTransitionOptions,
|
|
105
|
+
): Promise<{ applied: boolean; stage: number; reason?: string }> {
|
|
106
|
+
if (!context.runId) throw new Error('release traffic event requires graph runId');
|
|
107
|
+
if (!options.trafficFile) throw new Error('traffic file path is not configured');
|
|
108
|
+
const runId = context.runId;
|
|
109
|
+
const version = context.input.version;
|
|
110
|
+
const path = resolve(options.trafficFile);
|
|
111
|
+
if (!existsSync(path)) throw new Error(`traffic file does not exist: ${path}`);
|
|
112
|
+
const log = options.log ?? ((name, fields) => debug.log('release-loop.traffic', name, fields));
|
|
113
|
+
const stage = STAGE[event];
|
|
114
|
+
const stamp = (options.now ?? (() => new Date()))().toISOString();
|
|
115
|
+
const rate = event === 'gate-passed' ? TRAFFIC_PER_CYCLE.half : TRAFFIC_PER_CYCLE.full;
|
|
116
|
+
const releaseLifecycle = { runId, version, stage, event, ...(context.startedAt ? { startedAt: context.startedAt } : {}), updatedAt: stamp };
|
|
117
|
+
const release = () => {
|
|
118
|
+
const unlock = acquireTrafficLock(path, options.lockTimeoutMs);
|
|
119
|
+
try {
|
|
120
|
+
const before = readTrafficFile(path);
|
|
121
|
+
const prior = before.releaseLifecycle;
|
|
122
|
+
const sameRun = prior?.runId === runId;
|
|
123
|
+
const newerVersion = !prior?.runId || compareReleaseVersions(version, prior.version) > 0;
|
|
124
|
+
const sameVersionLaterRun = prior?.version === version && isLaterRun(context.startedAt, prior.startedAt);
|
|
125
|
+
if (sameRun && prior.stage >= stage) return { file: before, applied: true, idempotent: true };
|
|
126
|
+
const permitted = sameRun
|
|
127
|
+
? prior.stage === stage - 1
|
|
128
|
+
: newerVersion || (event === 'gate-passed' && prior?.version === version && (prior.stage ?? 0) < 2 && sameVersionLaterRun)
|
|
129
|
+
|| (event === 'verify-complete' && prior?.version === version && sameVersionLaterRun);
|
|
130
|
+
if (!permitted) {
|
|
131
|
+
return { file: before, applied: false, idempotent: false, reason: prior?.runId && prior.runId !== runId ? 'different-run-is-current' : 'event-out-of-order' };
|
|
132
|
+
}
|
|
133
|
+
const after: TrafficFile = {
|
|
134
|
+
...before,
|
|
135
|
+
...(event === 'verify-complete' ? { global: 'green' } : {}),
|
|
136
|
+
seats: Object.fromEntries(Object.entries(before.seats).map(([seat, value]) => [
|
|
137
|
+
seat, SEATS.includes(seat as typeof SEATS[number]) ? { ...value, perCycle: rate } : value,
|
|
138
|
+
])),
|
|
139
|
+
releaseLifecycle,
|
|
140
|
+
};
|
|
141
|
+
atomicWriteTrafficFile(path, after);
|
|
142
|
+
return { file: after, applied: true, idempotent: false };
|
|
143
|
+
} finally { unlock(); }
|
|
144
|
+
};
|
|
145
|
+
const result = release();
|
|
146
|
+
const reason = result.applied && result.idempotent ? 'already-applied' : result.reason;
|
|
147
|
+
log(result.idempotent ? 'already-applied' : result.applied ? 'applied' : 'ignored', {
|
|
148
|
+
runId, version, event, stage, reason, perCycle: rate, ...(event === 'verify-complete' ? { global: 'green' } : {}),
|
|
149
|
+
});
|
|
150
|
+
return { applied: result.applied, stage: result.file.releaseLifecycle?.stage ?? 0, ...(reason ? { reason } : {}) };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function trafficOptionsFromConfig(env: NodeJS.ProcessEnv = process.env, rawConfig: ReleaseLoopConfig = {}): TrafficTransitionOptions {
|
|
154
|
+
const config = rawConfig as ReleaseTrafficConfig;
|
|
155
|
+
const envTrafficFile = env.ELANOUS_RELEASE_TRAFFIC_FILE?.trim();
|
|
156
|
+
const configTrafficFile = typeof config.trafficFile === 'string' ? config.trafficFile.trim() : undefined;
|
|
157
|
+
const trafficFile = envTrafficFile || configTrafficFile;
|
|
158
|
+
return trafficFile ? { trafficFile } : {};
|
|
159
|
+
}
|