elanous 0.2.38 → 0.2.40
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/{RrjXTwz-urRAbChP1bX02 → HjeK7zONHfR7op1hKXp4k}/_buildManifest.js +1 -1
- package/apps/pwa/out/_next/static/chunks/{7954.4f7a8832663ee979.js → 1060.014f1f01a17aec26.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{8911.bf5e712acb25fb6c.js → 1281.d81877e35b9068f4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{2104.70e086d5174a4736.js → 1350.59f40e39e02d91b1.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1415.79821ec7ae99acdb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1452.162bb2d9d699e82a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/147.f92f63bbc5e85fa5.js +286 -0
- package/apps/pwa/out/_next/static/chunks/1537-6bb97b5214928ea9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1661.fb425a1d8a065b72.js +149 -0
- package/apps/pwa/out/_next/static/chunks/1671-1c22b40b50b208f2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1758.0204d3f48783c49d.js +90 -0
- package/apps/pwa/out/_next/static/chunks/1806.027bf78b6a94d340.js +104 -0
- package/apps/pwa/out/_next/static/chunks/2047.8f23e210995d4548.js +2 -0
- package/apps/pwa/out/_next/static/chunks/2061-7f2bbc53ae96c88b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1632.7d87f998e714bede.js → 2066.a77257c8c350770a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2096-a938496bc996dcbd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8019.21ca554a821dad01.js → 2177.df8547f9d7215c4e.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/2200-9a453b8f99687d49.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3528.e4b8ab70d339f965.js → 2226.2cf61ec781827219.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/2281-b8cede695c6c650e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6493.27ad77de7aac7966.js → 2499.02fbfd18126c10c0.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{7628.6b16863ac3972333.js → 2522.f2766b76d22c8bdf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{3086.fb14b60ce98b53d3.js → 2566.beaffc3a5e1fcd2e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{9299.24e2a7d9b6d1f91b.js → 261.0d471e8aded799f3.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/2615.3c9738153de3ef3f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2727.e703d99ac414916d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2752.7904b8df0254d0ae.js +27 -0
- package/apps/pwa/out/_next/static/chunks/2762-5051a862dfa40e99.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2767-31784bfedf8e245d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2774-719820e3f5b2a3eb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2812-f016aa0dda149287.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2829-734fe8bf2eee7347.js +1 -0
- package/apps/pwa/out/_next/static/chunks/311-32dcac79970985e8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3175-f93f7495cd28253c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3184.8da993f5502d2140.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3231-d7c981db0ff8bdcc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3250.151309c5176d6b4b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7524.ce973525d30b3d52.js → 3330.7443183b7e6fdbdf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3517.a5aa1797a9dbf2ee.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3567-45f676877ed9e8a5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3124-561f7f037943cde2.js → 3746-f80a78f220bf37b5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3819.8a1de79b2e8c7298.js +24 -0
- package/apps/pwa/out/_next/static/chunks/39.9b37b0be4c128a83.js +77 -0
- package/apps/pwa/out/_next/static/chunks/{bb6987f9.3647e1a23b0e3217.js → 3b355b3d.6f73c98bf6f9c749.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4023.a19045970a9ce16a.js +36 -0
- package/apps/pwa/out/_next/static/chunks/{8994d93e.33ba491f95345fc2.js → 409c787b.e48c5bec8ec10456.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{5041-5aada92ca215ffdd.js → 413-24c8284bda08241d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4135.e45d6ebc29e20647.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4283.65502f5d0e6ff6c0.js +287 -0
- package/apps/pwa/out/_next/static/chunks/4305-4e28fa92a7b34a5b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4335-50efa61fd7613142.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4351.0f37a6db2b34d50b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5638.107314dc479e2f37.js → 470.51a72a2348306455.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/4720.ecbd1693fa151068.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8487.291666583bebe640.js → 4769.9fd582071301f382.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{8509.3a54d4e61893d7cb.js → 483.725e4a61be922788.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{9fe1b809.738da85c8b17e3fb.js → 4864a2ca.1c1c280a7772c5e5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4969-4b209d7307410c00.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5068-c7f53862f83c0917.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5315.bc96711c24dfefe2.js +74 -0
- package/apps/pwa/out/_next/static/chunks/{8879.ac06755bcf3e44a1.js → 5383.bdfa5d886e39a826.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{6142.913b44e260a3f925.js → 5436.fdf6fa8ae3e9588d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5542.efbf14f16baed4dd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5622.94893f5aea7185d0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5644.8f401a25601d05fc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5658.35f70eb9871a54bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5721.7bdc0e904c76ab51.js +8 -0
- package/apps/pwa/out/_next/static/chunks/5790-eef0ca20884a6bdf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5802.ef0d8598ca68fe84.js +62 -0
- package/apps/pwa/out/_next/static/chunks/5890.fb974729788dcb71.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5924.5ef25cddd36d5dbf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5979-e59c099ebf8d1f35.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6020.714cfeada2315e2e.js +94 -0
- package/apps/pwa/out/_next/static/chunks/6027-dfe0349d8c50f698.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7161-8f22db3ec5ed005e.js → 6051-0ae955eedb10adb9.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/6178.bc4730c86cf74536.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3291.f6c1005387d514bd.js → 6257.c0037b8949239f5e.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/6273.d808b0dcb3c2b155.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6292.6ef1df64fa6a4c8a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/647-af8dd920ba46533c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6541-170dc5c59e135c0c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6624.981ef7caeb680560.js +32 -0
- package/apps/pwa/out/_next/static/chunks/6742-a1ad4a94ba7bc37a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5203.51e5d8b5f1dcfe53.js → 6749.0744ce6eb08d40e9.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/6836-493dc55c39187949.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6856-53ac421c06039a18.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1938.0e6c60ee91c4d7f5.js → 6908.06008c67f6380ea9.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/6915.096931864206f522.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6925.5e181fb7dcf90193.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{de8b930d.d94c0f42acf807fc.js → 6cc1d1d6.9a98dd4622f1afc6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7203-800e11037adc41eb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7219-15bdd9a80661af8e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7279.6170f591105936bb.js +93 -0
- package/apps/pwa/out/_next/static/chunks/7349-373165c7fbdcc3df.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7514-a533ba0ca1cae4f4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7515.1ad6efe157fa4448.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7521.d919cc1fab80c1d9.js +24 -0
- package/apps/pwa/out/_next/static/chunks/7538.90e97bbdf9178cff.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7611.85f045bf232053ac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7825-39203196a12579b2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7869.5e1a9a12fd70d6c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8086.1ab9103c41be884f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8099.1cccf31c1baa7e1c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8179-e61fffbdb70f1152.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8265.bdeb991b34db6a06.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1026-44010f405704050c.js → 8272-025653884f9645d2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{5307.d7328020227880c2.js → 8357.1aaa713b052a78b1.js} +4 -4
- package/apps/pwa/out/_next/static/chunks/8638-e42615f2166165b5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{4199-f7c6eb40cd0322f8.js → 8639-92c11d069792429e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8691.54e22831d5e8ccf8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8d173e38.18a2f7b4e1848bdb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9069.657590a84afb8fac.js +41 -0
- package/apps/pwa/out/_next/static/chunks/9125-ae588d88a0293b93.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9175.2de5209a5ce6f68a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9184.45afbf44fec87574.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{eaad0cba.771200940c529df7.js → 9189505a.980427ac048c8c60.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9311.f9f641a46a508611.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9371-a6fee8378ebfa3ee.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9421-3aa9ff4fa7f63d8a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9992.07ce73c10bd5e6a1.js +79 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-76394dc618d322c8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-1b948637c37b3abd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-c63400f1bcf3e47c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-95d0957267819586.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-866aac5ff226a7f3.js → page-ba957ff875a026a0.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-bb6fd0714879cb87.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-8f9038520d2acbef.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-d4b48397e05fce2c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-72d5744245dc0c27.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-37625dc8300fa43a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-fbef9d160272dbc1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-58424168df84928e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-72c12ba430ac57c0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-d53f3e9ed806d7e0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-4d2fbd45feee0829.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-76394dc618d322c8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-8783dc0ed7590a2c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-3706cb390606e930.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-9b0a86598e0111fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-4edb8df90f4537ce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-83d27757e2b8060a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-47398b9ba1cc5e1b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-fb86f55cfe2e681d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-e22345fc8fd47b1b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-fca2e59a81744d9e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-6e96bda63100b31e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-4d17ad8e0db8cea4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-121f92766c33beaa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-43f14212c545f92d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-ccec2023e7a974ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-b04f33f548b89a94.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-c8792823c0917021.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-8c0e32782ceef4cb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-94bd3922eb230ce8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-bca565887bafa03c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-ac1dffafe649fc6c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-52374edb5e23e455.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-ad4ebca120d43ca7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-76394dc618d322c8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-718b89ce4b4ae4e4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-204eca33eeeaddec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-e58ba9bcdb9ac0f9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-98f6dd50bafd7734.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-139d722de1dcd6c1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-5a4bdd72bff27057.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-6f6ab17d44f984ce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-aabb7bd15fb72da9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-e4322319164a4436.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-e9efc0c8f32e2371.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-ccde99f07aaf71f7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-80464aced9614390.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{b7478fef.5917716286213fcc.js → b84cad00.594027ebe0531fe2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/c696986b.5dc8bb1143af16d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{b7459770.6539b11860eb8a9c.js → ca3702f8.2913182e73889fa2.js} +7 -7
- package/apps/pwa/out/_next/static/chunks/{3f0de3de-2340c532c00395db.js → d5d3cefc-373c4eb722ed848b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{89d6c04f-6b8ab341748e468a.js → d8de0de2-dd0f4ac8e5ab1832.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{framework-4b12a946f352cee2.js → framework-002b472a66935d31.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/main-49b88d47292c0476.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-fff2549cb10a133f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-32abbbba3f3b76b7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-9d9cfef60c60dd7a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-cc7f483fa294034a.js +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +22 -22
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +22 -22
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +22 -22
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +21 -21
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +21 -21
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +22 -22
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +21 -21
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +22 -22
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +22 -22
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +21 -21
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +22 -22
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +22 -22
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +22 -22
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +22 -22
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +21 -21
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +21 -21
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +22 -22
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +22 -22
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +22 -22
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +21 -21
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +21 -21
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +21 -21
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +22 -22
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +22 -22
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +21 -21
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +21 -21
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +21 -21
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +21 -21
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +22 -22
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +22 -22
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +21 -21
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +22 -22
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +22 -22
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +22 -22
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +22 -22
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +22 -22
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +21 -21
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +22 -22
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +22 -22
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +22 -22
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +22 -22
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +22 -22
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +22 -22
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +22 -22
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +22 -22
- package/graphs/demo/persona-marketer/fixtures/marketer-scenario.yaml +10 -0
- package/graphs/demo/persona-marketer/persona-marketer.yaml +25 -0
- package/graphs/demo/persona-marketer/recipes.yaml +19 -0
- package/graphs/video/explainer-line.yaml +4 -1
- package/package.json +1 -1
- package/scripts/dead-code/report.ts +169 -0
- package/scripts/lib/morning-report-options.ts +3 -4
- package/scripts/lib/tui-regress-evidence.ts +56 -0
- package/scripts/morning-report.ts +5 -1
- package/scripts/public-export.ts +15 -7
- package/scripts/release-loop/export-check-node.ts +21 -3
- package/scripts/release-loop/gate-node.ts +379 -48
- package/scripts/release-loop/gate-shard-dispatch.ts +128 -0
- package/scripts/release-loop/gate-shard-receipts.ts +89 -0
- package/scripts/release-loop/gate-wall.ts +5 -5
- package/scripts/release-loop/preflight.ts +6 -2
- package/scripts/release-loop/resume-release.ts +20 -5
- package/scripts/release-loop/resume.ts +48 -0
- package/scripts/release-loop/version-node.ts +7 -2
- package/scripts/release-story/draft.ts +1 -1
- package/scripts/research-bench/README.md +53 -0
- package/scripts/research-bench/questions.json +66 -0
- package/scripts/research-bench/rubric.md +33 -0
- package/scripts/research-bench/run.ts +523 -0
- package/scripts/tui-regress.ts +85 -10
- package/src/acp/core-turn-bridge.ts +8 -0
- package/src/agent/autonomous-tools.ts +10 -3
- package/src/agent/monad-agent-turn.ts +4 -1
- package/src/agent/registry.ts +26 -2
- package/src/agent/shared-app-tools.ts +1 -1
- package/src/agent/surface-ux/build.ts +12 -3
- package/src/agent/surface-ux/types.ts +1 -1
- package/src/agent/task-notification.ts +75 -6
- package/src/agent/types.ts +3 -0
- package/src/agent-mission/driver.ts +29 -3
- package/src/agent-mission/mission-cli.ts +2 -1
- package/src/agent-substrate/review-context-pack.ts +365 -0
- package/src/agent-substrate/self-review-cli.ts +226 -17
- package/src/ask-user-question/index.ts +6 -0
- package/src/ask-user-question/inline-resolver.ts +49 -0
- package/src/auto-research/question-ladder-contract.ts +22 -3
- package/src/auto-research/research-turn.ts +350 -17
- package/src/autopilot/agent-loop.ts +1 -1
- package/src/autopilot/pty-control-loop.ts +8 -1
- package/src/boot/daemon-tools/index.ts +4 -3
- package/src/boot/daemon-tools/self-implement.ts +270 -2
- package/src/boot/daemon-tools/types.ts +2 -0
- package/src/budget/llm-cost.ts +44 -5
- package/src/budget/unified-usage.ts +17 -1
- package/src/capture/tui-self-report.ts +14 -0
- package/src/chat/index.ts +33 -7
- package/src/cli/agent-cli.ts +43 -8
- package/src/cli/intake-cli.ts +22 -1
- package/src/cli/lesson-cli.ts +19 -3
- package/src/cli/log-event-names.ts +1 -0
- package/src/cli/pod-cli.ts +17 -2
- package/src/cli/pty-takeover-cli.ts +67 -7
- package/src/cli/release-cli.ts +52 -6
- package/src/cli/repo-cli.ts +2 -1
- package/src/cli/tasks-cli.ts +36 -9
- package/src/control-plane/ledger.ts +21 -4
- package/src/core-turn/run-core-turn.ts +44 -3
- package/src/core-turn/run-goal-loop.ts +152 -8
- package/src/core-turn/types.ts +9 -0
- package/src/dashboard/chat/acp-chat.ts +2 -2
- package/src/dashboard/classic-sigquit.ts +16 -0
- package/src/dashboard/index.ts +321 -27
- package/src/dashboard/input/chat-main-interaction-opts.ts +3 -0
- package/src/dashboard/input/chat-main-plain-turn-runtime.ts +7 -2
- package/src/dashboard/input/chat-main-slash-command.ts +7 -0
- package/src/dashboard/input/dashboard-plain-turn-stream.ts +4 -0
- package/src/dashboard/input/dashboard-priority-key-route.ts +21 -5
- package/src/dashboard/input/external-input-host.ts +2 -0
- package/src/dashboard/sidetab-runtime.ts +228 -0
- package/src/dashboard/slash-runtime/ask-clarification.ts +7 -1
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +20 -8
- package/src/dashboard/slash-runtime/help-from-registry.ts +2 -2
- package/src/dashboard/term-slash-runtime.ts +16 -18
- package/src/dashboard/turn-preamble.ts +2 -0
- package/src/domains/knowledge.ts +1 -1
- package/src/esc-abort-gate.ts +33 -1
- package/src/execution-loop/launch-gate.ts +206 -1
- package/src/graph-runner/graph-cli.ts +6 -4
- package/src/graph-runner/runner.ts +31 -9
- package/src/graph-wizard/generate.ts +16 -7
- package/src/graph-wizard/mission-compose.ts +201 -0
- package/src/grok/agent-search.ts +8 -2
- package/src/grok/credential.ts +5 -0
- package/src/grounding/ref-index.ts +11 -0
- package/src/grounding/ref-vector.ts +61 -0
- package/src/grounding/sources-cli.ts +2 -2
- package/src/grounding/sources.ts +15 -1
- package/src/harness/harness-cli-command.ts +38 -9
- package/src/harness/harness-pod-dispatch.ts +7 -1
- package/src/index.ts +18 -4
- package/src/input/external-input-queue.ts +10 -0
- package/src/intake-plane/collect-telegram-saved.ts +36 -1
- package/src/intake-plane/intake-to-tasks.ts +246 -3
- package/src/lessons/friction-ledger.ts +154 -0
- package/src/live-supply/collector.ts +149 -0
- package/src/live-supply/probes.ts +52 -0
- package/src/live-supply/snapshot.ts +40 -0
- package/src/llm.ts +431 -76
- package/src/loops/orchestrator/tick.ts +8 -0
- package/src/nexus/api/harness-api.ts +2 -1
- package/src/pty/snapshot.py +132 -0
- package/src/pty-shell/pty-control-ipc.ts +29 -2
- package/src/pty-shell/pty-manifest.ts +59 -3
- package/src/pty-shell/registry.ts +2 -0
- package/src/pty-shell/tui-run-state.ts +80 -3
- package/src/release-loop/gate-host-reservation.ts +13 -4
- package/src/release-loop/gate-shard-ledger.ts +68 -0
- package/src/release-loop/merged-pr-checklist.ts +25 -2
- package/src/release-loop/rehearsal-shard-plan.ts +6 -1
- package/src/release-loop/rehearsal.ts +27 -0
- package/src/research/edge-decision.ts +124 -15
- package/src/research/gap-followup.ts +249 -0
- package/src/research/lenses/counter-evidence.ts +100 -0
- package/src/research/lenses/working-backwards.ts +89 -0
- package/src/research/plugin-discovery.ts +242 -0
- package/src/research-bridge/invoke.ts +4 -2
- package/src/research-bridge/types.ts +2 -0
- package/src/self-dev/ask-launch-flow.ts +34 -106
- package/src/self-dev/dev-cli.ts +2 -0
- package/src/self-dev/dev-pipeline.ts +8 -4
- package/src/self-dev/graph-journey-route.ts +10 -2
- package/src/self-dev/launch-preflight-text.ts +2 -2
- package/src/self-dev/launch-preflight.ts +38 -7
- package/src/self-dev/orchestrate-cli.ts +1 -0
- package/src/self-dev/run-supervisor.ts +19 -0
- package/src/self-implement/device-first.ts +8 -2
- package/src/self-implement/doctor-visit.ts +76 -58
- package/src/self-implement/gate-artifacts.ts +3 -0
- package/src/self-implement/gate-baseline.ts +75 -4
- package/src/self-implement/gate-cli.ts +317 -9
- package/src/self-implement/gate-receipt-registry.ts +180 -0
- package/src/self-implement/gate-remote.ts +145 -22
- package/src/self-implement/goal-author-clarification.ts +178 -24
- package/src/self-implement/goal-author-decompose.ts +24 -15
- package/src/self-implement/goal-author.ts +9 -5
- package/src/self-implement/host-regate.ts +120 -45
- package/src/self-implement/impl-findings-feedback.ts +188 -0
- package/src/self-implement/knowhow-pack.ts +189 -10
- package/src/self-implement/off-diff-evidence.ts +124 -0
- package/src/self-implement/orchestrator.ts +85 -7
- package/src/self-implement/run-ledger.ts +46 -8
- package/src/self-implement/run-stop.ts +12 -0
- package/src/self-implement/seams.ts +6 -3
- package/src/self-implement/self-implement-cli.ts +4 -2
- package/src/session/chat.ts +89 -15
- package/src/session-runtime/provider-circuit.ts +140 -0
- package/src/shell-runner/file-engine.ts +1 -0
- package/src/shell-runner/types.ts +4 -0
- package/src/sidetab-core/model.ts +176 -0
- package/src/skills/tools/schedule-wakeup.ts +23 -1
- package/src/skills/tools/shell.ts +51 -5
- package/src/steward/directive-audit.ts +354 -0
- package/src/surface/child-process.ts +31 -0
- package/src/surface/tui-stderr-owner.ts +48 -0
- package/src/task-agent/cover.ts +30 -12
- package/src/task-agent/judge.ts +56 -0
- package/src/task-agent/postland-card.ts +35 -2
- package/src/task-agent/rejudge-on-head.ts +46 -2
- package/src/task-agent/repair-cycle.ts +1 -1
- package/src/task-agent/task-hand.ts +129 -24
- package/src/task-agent/tier-split.ts +105 -21
- package/src/task-orchestrator/surfaces/pod-command-job.ts +330 -2
- package/src/task-orchestrator/surfaces/pod-config-snapshot.ts +123 -0
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +27 -2
- package/src/task-orchestrator/surfaces/pod-mirror-seed.ts +11 -8
- package/src/task-orchestrator/surfaces/pod-si-warm-protocol.ts +1 -1
- package/src/task-orchestrator/surfaces/pod-source-receive.ts +74 -6
- package/src/task-orchestrator/surfaces/pod-warm-worker-pod.ts +2 -1
- package/src/task-orchestrator/surfaces/pod-warm-worker-provider.ts +56 -15
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +186 -33
- package/src/tool-runtime/skill-exec-runtime.ts +95 -5
- package/src/tui/entry.ts +15 -2
- package/src/tui/inline-choice.ts +188 -0
- package/src/tui/sidetab/list.ts +2 -176
- package/src/tui/slash/term.ts +12 -1
- package/src/tui-client/dashboard-session.ts +7 -1
- package/src/user-config.ts +34 -4
- package/src/ux-sim/classic-tui-seat-scenarios.ts +1 -7
- package/src/ux-sim/fake-land-queue.ts +88 -0
- package/src/ux-sim/gate-shard-host-reroute.ts +82 -0
- package/src/ux-sim/goal-interim-trace.ts +24 -0
- package/src/ux-sim/next-tui-seat-scenarios.ts +8 -13
- package/src/ux-sim/task-orchestrator-surfaces-self-implement-pod-device.ts +4 -1
- package/src/version/packed-revision.json +1 -1
- package/src/video-pipeline/recipes/explainer.ts +54 -2
- package/src/web-search/grok.ts +32 -6
- package/src/web-search/index.ts +8 -7
- package/src/workflow-runtime/node-catalog.ts +53 -4
- package/apps/pwa/out/_next/static/chunks/1118.a8a74ea29aac3e46.js +0 -27
- package/apps/pwa/out/_next/static/chunks/1204-2aa830479adf80f4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1341-43b7d2dc77d45c9d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1440-c3ea8882badfd9bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1698-9c11cbf97f13717e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1770-3b1fc92253ef9f2f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1923-5c1695332240246a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/213-20675f20673ba22a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/214.186a430da28338f1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2277.3bd4c0ac7f1b5996.js +0 -93
- package/apps/pwa/out/_next/static/chunks/240.e09f433f71c78ff4.js +0 -104
- package/apps/pwa/out/_next/static/chunks/2415.738b777642a02233.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2417.02151393605a7cd0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2463.cd2641a76daaa9ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2576.b9e9c629c4b84c52.js +0 -1
- package/apps/pwa/out/_next/static/chunks/265-c021de53f0865bd9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2998-b1cc13956cd6a6e7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2e4782d3.ba2f0ab2b63a53e7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3130.626b54382b4dc379.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3221-1bb3f99a55088613.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3280.24edb1ffd03a7f3f.js +0 -24
- package/apps/pwa/out/_next/static/chunks/3288-eb3c22a1cbf2ae68.js +0 -1
- package/apps/pwa/out/_next/static/chunks/33.ef99e8374f96c6c4.js +0 -36
- package/apps/pwa/out/_next/static/chunks/3341-f09b9efbd7a82f70.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3389.012c70767a872f9b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3410-d7c0f14b329d6fda.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3421.c87d6313a7d670f7.js +0 -287
- package/apps/pwa/out/_next/static/chunks/3422-83a3953f9bd67e26.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3587-002e24e368c515a9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3644.f12a7d821ab940af.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3660.d7516588e2d875c3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3695.b82e2610f4cd4ea9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3858.ca1f5bc0f577eac9.js +0 -94
- package/apps/pwa/out/_next/static/chunks/3959-e6ac4a139d9d4bcc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4032.53f62176514ec0ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4204-ef8484cbecd5d62f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4259-163129ca79913c4b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/428-eaa77df3d0c85969.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4432-97e400ffa1e161f5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/449-aa427cf2cbb75f50.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4491-39a0fd32bbaf3840.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4588.b7cf06d382953979.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4690.70ec8060825366ab.js +0 -32
- package/apps/pwa/out/_next/static/chunks/4804.6012b5cd33c22792.js +0 -90
- package/apps/pwa/out/_next/static/chunks/4881.e4d22d730e3b9df5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4937.ce2e340dc29cad3e.js +0 -79
- package/apps/pwa/out/_next/static/chunks/5-bb1c08d84bd71df3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5005.de98df23b37cdf3c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5035.dc64a89cad5573cc.js +0 -74
- package/apps/pwa/out/_next/static/chunks/5207.1a224035455790fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5255-1b60d50eabe9f057.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5269.d0436ee6d773382a.js +0 -286
- package/apps/pwa/out/_next/static/chunks/5300.677c97e0393742e6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5309.3367c4d50f52b489.js +0 -1
- package/apps/pwa/out/_next/static/chunks/552-689827d49780fa00.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5539.34cc9d60622cb69b.js +0 -8
- package/apps/pwa/out/_next/static/chunks/5568.218dea0c00ca2ec7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5645.b7e3b055455a04ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5662.18cc0f30353cbd96.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5671.da24fc9dc5e98325.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5b141171.324c40fd49cb6821.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6175-d4621f6a10b0cfab.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6290.3eb53a20746b72ce.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6415-9307f3eafa90837f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6579-3de35d00aacd71b5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/680-2f324c4e039a5b67.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6845-331b3c7481da3ba3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7000.28157393caaf5343.js +0 -62
- package/apps/pwa/out/_next/static/chunks/7311-60a7ea80d23c7521.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7401.525d0a12f7d02c06.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7438.c2cf72b66dca6aa4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7488.966b116789f3d41a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7573.2376e13c9c21f1f8.js +0 -77
- package/apps/pwa/out/_next/static/chunks/7577.ea6e9ac72e035520.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7657.8d1fb3d2f7ebe273.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7903-1435d5d2f0dd7807.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7978-30a91192c5dbe3c2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8025-8795821525cf1a07.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8191.3674a00f0b565c23.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8217-604955d8885c5ef2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8298-eed6f5f2585a63f4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8758.ac421f726a189125.js +0 -1
- package/apps/pwa/out/_next/static/chunks/891.d6c723a51a067b36.js +0 -149
- package/apps/pwa/out/_next/static/chunks/9223.cbcc88e0e9950426.js +0 -41
- package/apps/pwa/out/_next/static/chunks/9317.6a9869039c231bb3.js +0 -24
- package/apps/pwa/out/_next/static/chunks/9383.e4755f275b3c11ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9581.da63ddf41acfad46.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9715.8c5529a0a5c41a36.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9817.3009d65f0889ef1f.js +0 -2
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-76873dd80da4f715.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-ed32b6ec5dea8f23.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-fdd40e15babf3d25.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-384efc78e85e400b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-d91809c5aeed319a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-128f3522f89a81ab.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-595961abf3b43ad1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-f41dccb76d8c0263.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-a3205c2e28b7d0d9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-c39a80277814a3f4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-1ab3f98194513195.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-a5246f7e254d77d0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-0f92287bdb81217d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-5a37131ff63793ef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-76873dd80da4f715.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-ac4bd14bb2348bfd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-c348a3959c5b5fc6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-2addd81c0d3e2906.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-da6b765f58a3eefb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-0661e40f8780c32c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-b89af1b1c8e4875b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-da8aeb82ba751cdb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-cc71317b239439db.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-bde7fc1d64ffe369.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-e2797214f713fa58.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-2a35bcda70defaf6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-3af03deedde45032.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-d1d1210f99f620ae.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-46d1b5500885861d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-291ab19fe2acb173.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-7f569e533ddeecba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-990129c9fee46d8f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-7737f785ebb1cdaf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-9f3c73a4f7ffd2f8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-03be7fb347f01058.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-96b0280625e4df2b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-56855545049eb56d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-76873dd80da4f715.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-79a909c9981f0338.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-a9d9a3e66459c1e0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-c383cbf2013f7069.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-f57c6a2e54dbdca4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-6b140f77593576eb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-6b767ae2d9d0adfc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-116d6198d5da0e3d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-b9c9b778c7501370.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-a6c78392ab8f6335.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-455183deb4ed0cfe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-6a63e99526bd63d0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31c22caafe788c08.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-941155250ed2644b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-f8b6765abed6d149.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-78432194c85388f9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-cdf7390829512b1b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-d5f0964a9c2e92d2.js +0 -1
- /package/apps/pwa/out/_next/static/{RrjXTwz-urRAbChP1bX02 → HjeK7zONHfR7op1hKXp4k}/_ssgManifest.js +0 -0
|
@@ -43,6 +43,9 @@ import { resolveWarmPoolProfile } from '../../src/self-implement/pod-warm-pool-p
|
|
|
43
43
|
import { decideWarmReuse, type WarmReuseSwitchConfig, type WarmReuseEvidence } from '../../src/task-orchestrator/surfaces/pod-warm-switch.js';
|
|
44
44
|
import { getUserConfig } from '../../src/user-config.js';
|
|
45
45
|
import { beginGateShardAttempt, finalizeTerminalGateShards, gateShardsPath, recordGateShardsVerdict, writeGateShards, type GateJobTerminal, type GateShard } from '../../src/release-loop/gate-shards.js';
|
|
46
|
+
import { appendGateShardLedger, gateShardLedgerPath, type GateShardLedgerRow } from '../../src/release-loop/gate-shard-ledger.js';
|
|
47
|
+
import { markGateShardUnmeasured, nextGateShardAttempt, readGateShardManifest, readGateShardResult, saveGateShardResult } from './gate-shard-receipts.js';
|
|
48
|
+
import { planGateShardDispatch, type GateShardReceipt } from './gate-shard-dispatch.js';
|
|
46
49
|
import { defaultKubectl } from '../../src/task-orchestrator/surfaces/self-implement-pod.js';
|
|
47
50
|
import { executeTuiNodeCheck, type ExecutedTuiCheck } from './tui-node-checks.js';
|
|
48
51
|
import { runWithReleaseGateTraffic } from './gate-traffic-mode.js';
|
|
@@ -218,6 +221,12 @@ export interface GateOptions {
|
|
|
218
221
|
recheckFileLimit?: number;
|
|
219
222
|
/** freshShards — GATE-SHARD-RESUME off: every shard starts a Pod even when this logDir already holds its result. */
|
|
220
223
|
pod?: { pool: string; shards?: number; shardTimeoutSeconds?: number; durationSource?: string; bunCache?: string; freshShards?: boolean;
|
|
224
|
+
/** Remeasure one named shard, or all incomplete shards with auto. Complete siblings keep their receipts. */
|
|
225
|
+
rerouteShard?: string | 'auto';
|
|
226
|
+
/** Pool context for the rerouted shard; the planner otherwise distributes missing shards over pool contexts. */
|
|
227
|
+
shardHost?: string;
|
|
228
|
+
/** Shard capability requirements are independent of host names; placement resolves them at run time. */
|
|
229
|
+
shardCapabilities?: Readonly<Record<string, { os?: 'darwin' | 'linux' }>>;
|
|
221
230
|
/** Opt-in gate shadow: provider fills the fenced lease identity. The Job remains authoritative. */
|
|
222
231
|
warmReplay?: (input: Omit<WarmWorkerCommand, 'leaseId' | 'generation' | 'workerId' | 'key'>) => Promise<{ command: WarmWorkerCommand | null; transport: WarmPodTransportResult }>;
|
|
223
232
|
warmQuarantine?: (command: WarmWorkerCommand, reason: string) => Promise<void>;
|
|
@@ -253,8 +262,8 @@ export interface GateOptions {
|
|
|
253
262
|
hostMirrorReaders?: GateHostMirrorReaders;
|
|
254
263
|
/** GATE-INSTALL-CACHE — concurrent `bun install`s per node (`release.loop.gatePodInstallSlots`); omitted = 4 · 0 = off. */
|
|
255
264
|
installSlots?: number;
|
|
256
|
-
/** GATE-MEM-ADMIT — seconds
|
|
257
|
-
*
|
|
265
|
+
/** GATE-MEM-ADMIT — seconds an active shard waits for pool admission. Root shards beyond the pool cap queue
|
|
266
|
+
* outside this timer; an exhausted wait stays unmeasured (`release.loop.gatePodAdmissionWaitSeconds`); 0 = off. */
|
|
258
267
|
admissionWaitSeconds?: number;
|
|
259
268
|
/** GATE-MEM-ADMIT — an injected admission (tests · embedders); config never sets it. Absent = the gate's own pool's. */
|
|
260
269
|
admission?: GateAdmission;
|
|
@@ -279,7 +288,8 @@ export interface GateOptions {
|
|
|
279
288
|
* of {@link gateShardAbortScript} on the gate's own pool; an injected pool without it runs without the mid-run watch. */
|
|
280
289
|
abortTests?: (job: { name: string; context?: string }, reason: string) => boolean | void;
|
|
281
290
|
/** GATE-LIVE-OBS — the shard state file (`gateShardsPath(version)`) this sweep keeps current; absent = not written. */
|
|
282
|
-
shardsFile?: { path: string; version: string
|
|
291
|
+
shardsFile?: { path: string; version: string; ledgerPath?: string; envFingerprint?: string;
|
|
292
|
+
receiptRoot?: string; base40?: string; expectedImageDigest?: string | null };
|
|
283
293
|
/** GATE-RISK-LANE — the ledgers `gateHistory` reads its watch list from; absent = no risk lane (the sweep plans as before). */
|
|
284
294
|
riskWatch?: { instanceRoots: readonly string[]; ledgerRoots: readonly string[] };
|
|
285
295
|
/** GATE-MULTI-HOST — is this pool member reachable (kubectl ns ⊕ ssh)? Injected by tests; absent = the real probe on
|
|
@@ -523,6 +533,31 @@ const lastMatch = (output: string, pattern: RegExp): RegExpExecArray | null => {
|
|
|
523
533
|
return last;
|
|
524
534
|
};
|
|
525
535
|
const summaryCount = (output: string, label: string) => Number(lastMatch(output, new RegExp(`(?:^|\\n)\\s*(\\d+) ${label}s?\\s*(?=\\n|$)`))?.[1] ?? NaN);
|
|
536
|
+
/** `test:deterministic` runs several Bun commands. Its final clean command is not the verdict for earlier commands. */
|
|
537
|
+
function deterministicSubrunCounts(output: string): { fail: number; error: number } | undefined {
|
|
538
|
+
if (!/error: script "test:deterministic" exited with code 1/.test(output)) return undefined;
|
|
539
|
+
const lines = output.split(/\r?\n/).map((line) => line.trim());
|
|
540
|
+
const runs: Array<{ fail: number; error: number }> = [];
|
|
541
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
542
|
+
if (!/^Ran \d+ tests? across [1-9]\d* files?\./.test(lines[i]!)) continue;
|
|
543
|
+
let pass: number | undefined;
|
|
544
|
+
let fail: number | undefined;
|
|
545
|
+
let error = 0;
|
|
546
|
+
for (let j = i - 1; j >= Math.max(0, i - 8); j -= 1) {
|
|
547
|
+
const line = lines[j]!;
|
|
548
|
+
const count = /^(\d+) (pass|fail|skip|errors?|expect\(\) calls)$/.exec(line);
|
|
549
|
+
if (!line) continue;
|
|
550
|
+
if (!count) break;
|
|
551
|
+
if (count[2] === 'pass') { pass = Number(count[1]); break; }
|
|
552
|
+
if (count[2] === 'fail') fail = Number(count[1]);
|
|
553
|
+
if (count[2] === 'error' || count[2] === 'errors') error = Number(count[1]);
|
|
554
|
+
}
|
|
555
|
+
if (pass === undefined || fail === undefined) return undefined;
|
|
556
|
+
runs.push({ fail, error });
|
|
557
|
+
}
|
|
558
|
+
if (runs.length < 2) return undefined;
|
|
559
|
+
return runs.reduce((sum, run) => ({ fail: sum.fail + run.fail, error: sum.error + run.error }), { fail: 0, error: 0 });
|
|
560
|
+
}
|
|
526
561
|
/** bun reads a bare positional as a substring filter (`test` matches every *.test.ts); `./` makes it a path. */
|
|
527
562
|
const asPath = (path: string) => path.startsWith('./') || path.startsWith('/') ? path : `./${path}`;
|
|
528
563
|
/** A Pod path (`~/repo/src/…` · `../..~/repo/src/…`) as a repository-relative one. */
|
|
@@ -1045,20 +1080,29 @@ export function gateShardAbortScript(reason: string, graceSeconds = GATE_SHARD_A
|
|
|
1045
1080
|
return [
|
|
1046
1081
|
`reason=${quote(reason.replace(/[^\w .:-]/g, '_'))}`,
|
|
1047
1082
|
`only=${quote(outbox ?? '')}`,
|
|
1048
|
-
|
|
1083
|
+
// Read after the final ')' because comm can itself contain spaces and ')'. starttime is field 20 in the suffix
|
|
1084
|
+
// (field 22 in proc stat); pairing it with PID prevents a recycled PID from validating an old group number.
|
|
1085
|
+
"proc_identity() { sed 's/^.*) //' \"/proc/$1/stat\" 2>/dev/null | awk '{print $3, $20}'; }",
|
|
1086
|
+
"pg_of() { proc_identity \"$1\" | awk '{print $1}'; }",
|
|
1049
1087
|
'root_pg=$(pg_of 1); my_pg=$(pg_of $$); found=0',
|
|
1050
1088
|
'for d in /proc/[0-9]*; do',
|
|
1051
1089
|
' pid=${d#/proc/}',
|
|
1052
1090
|
" out=$(tr '\\0' '\\n' < \"$d/cmdline\" 2>/dev/null | grep -m1 -E '^--reporter-outfile=.*/outbox/part-[0-9]+\\.junit\\.xml$') || continue",
|
|
1053
1091
|
' if [ -n "$only" ]; then case "$out" in "--reporter-outfile=$only/part-"*) ;; *) continue;; esac; fi',
|
|
1054
|
-
'
|
|
1055
|
-
'
|
|
1092
|
+
' identity=$(proc_identity "$pid")',
|
|
1093
|
+
' pg=${identity%% *}; started=${identity#* }',
|
|
1094
|
+
' case "$pg:$started" in *[!0-9:]*|:|*:|:*) continue;; esac',
|
|
1056
1095
|
' if [ "$pg" != "$pid" ] || [ "$pg" -le 1 ] || [ "$pg" = "$root_pg" ] || [ "$pg" = "$my_pg" ]; then continue; fi',
|
|
1096
|
+
// Re-read the process identity immediately before signaling. The same check guards delayed KILL so a PID/PGID
|
|
1097
|
+
// that disappeared and was reused during the grace interval is never signaled as if it were the original test.
|
|
1098
|
+
' [ "$(proc_identity "$pid")" = "$identity" ] || continue',
|
|
1057
1099
|
' out=${out#--reporter-outfile=}; dir=${out%/part-*}; part=${out##*/part-}; part=${part%.junit.xml}',
|
|
1058
|
-
'
|
|
1059
|
-
'
|
|
1060
|
-
'
|
|
1061
|
-
|
|
1100
|
+
' if [ "$(proc_identity "$pid")" = "$identity" ] && kill -TERM -- "-$pg" 2>/dev/null; then',
|
|
1101
|
+
' printf "%s\\n" "$reason" > "$dir/part-$part.abort"',
|
|
1102
|
+
' [ -f "$dir/shard.timeout" ] || echo "$part" > "$dir/shard.timeout"',
|
|
1103
|
+
' found=$((found + 1)); echo "ELANOUS_GATE_ABORT part=$part pgid=$pg"',
|
|
1104
|
+
` ( sleep ${Math.max(1, Math.floor(graceSeconds))}; [ "$(proc_identity "$pid")" = "$identity" ] && kill -KILL -- "-$pg" 2>/dev/null ) >/dev/null 2>&1 </dev/null &`,
|
|
1105
|
+
' fi',
|
|
1062
1106
|
'done',
|
|
1063
1107
|
'echo "ELANOUS_GATE_ABORT signalled=$found"',
|
|
1064
1108
|
'[ "$found" -gt 0 ]',
|
|
@@ -1190,8 +1234,8 @@ const lastStartedTestFile = (output: string, paths: string[]): string | undefine
|
|
|
1190
1234
|
* split shape follows from depth and size alone, so the same plan reaches the same child names (TC 09:30 review ②).
|
|
1191
1235
|
* `runKey` hashes where and how the Pod ran the shard (pool · command · ignores · memory · deadline · Bun cache): a fix on the Pod side
|
|
1192
1236
|
* makes every old result a miss instead of re-reading an old failure as introduced (TC 09:48 review). */
|
|
1193
|
-
export function previousShardDescent(logDir: string, name: string, commit: string, files: readonly string[], runKey: string, parentAttempt?: string, splitChild?: string): { way: 'retry' | 'split'; attemptId: string } | undefined {
|
|
1194
|
-
const meta = (file: string): { commit?: unknown; files?: unknown; rc?: unknown; runKey?: unknown; attemptId?: unknown; parentAttempt?: unknown } | undefined => {
|
|
1237
|
+
export function previousShardDescent(logDir: string, name: string, commit: string, files: readonly string[], runKey: string, parentAttempt?: string, splitChild?: string): { way: 'retry' | 'split' | 'file-fanout'; attemptId: string } | undefined {
|
|
1238
|
+
const meta = (file: string): { commit?: unknown; files?: unknown; rc?: unknown; reason?: unknown; runKey?: unknown; attemptId?: unknown; parentAttempt?: unknown } | undefined => {
|
|
1195
1239
|
try { return JSON.parse(readFileSync(join(logDir, `${file}.json`), 'utf8')); } catch { return undefined; }
|
|
1196
1240
|
};
|
|
1197
1241
|
const own = meta(name);
|
|
@@ -1208,6 +1252,8 @@ export function previousShardDescent(logDir: string, name: string, commit: strin
|
|
|
1208
1252
|
&& (!whole || row.files.length === files.length);
|
|
1209
1253
|
};
|
|
1210
1254
|
if (child(`${name}-retry`, true)) return { way: 'retry', attemptId };
|
|
1255
|
+
// A stalled multi-file parent may have descended directly to one Pod per file. Keep that shape on resume.
|
|
1256
|
+
if (own.reason === 'stall' && files.length > 1 && child(`${name}-file-0`, false)) return { way: 'file-fanout', attemptId };
|
|
1211
1257
|
// Only the first child of the split the caller takes now (`-file-0` · `-c0` · `-0` · review r3): a record of another
|
|
1212
1258
|
// split shape is not followed.
|
|
1213
1259
|
return splitChild !== undefined && child(`${name}${splitChild}`, false) ? { way: 'split', attemptId } : undefined;
|
|
@@ -1239,9 +1285,11 @@ function failuresOf(run: CommandResult, label: string): SweepFailures {
|
|
|
1239
1285
|
const output = stripPodRepoRoot(run.output.replace(/\x1b\[[0-9;]*[A-Za-z]/g, ''));
|
|
1240
1286
|
const ran = lastMatch(output, /Ran (\d+) tests? across ([1-9]\d*) files?/);
|
|
1241
1287
|
const reportedErrors = summaryCount(output, 'error');
|
|
1242
|
-
|
|
1243
|
-
const
|
|
1244
|
-
const
|
|
1288
|
+
// Validated Pod shards were checked separately before their logs were joined; only the host sweep needs subrun totals.
|
|
1289
|
+
const subruns = !run.validatedShards && run.rc === 1 ? deterministicSubrunCounts(output) : undefined;
|
|
1290
|
+
const errorCount = subruns?.error ?? (Number.isNaN(reportedErrors) ? 0 : reportedErrors);
|
|
1291
|
+
const incomplete = (reason: string) => new Error(`${label} incomplete (rc=${run.rc}; Ran=${ran ? ran[0] : 'missing'}; errors=${subruns || !Number.isNaN(reportedErrors) ? errorCount : 'missing'}; ${reason})\n${tail40(output)}`);
|
|
1292
|
+
const count = subruns?.fail ?? summaryCount(output, 'fail');
|
|
1245
1293
|
const failures = parseFailures(output);
|
|
1246
1294
|
// Bun repeats failed test names under "N tests failed:". A validated Pod sweep joins
|
|
1247
1295
|
// separately checked shards, so a summary in one shard can precede real failures
|
|
@@ -1271,7 +1319,7 @@ function failuresOf(run: CommandResult, label: string): SweepFailures {
|
|
|
1271
1319
|
}
|
|
1272
1320
|
}
|
|
1273
1321
|
if (errors.length !== errorCount) throw incomplete(`error attribution identified=${errors.length}`);
|
|
1274
|
-
if (!Number.isFinite(count) || (count !== occurrences && !(run.validatedShards && occurrences > count && failures.length === count)) || (count > 0 && failures.length === 0)
|
|
1322
|
+
if (!Number.isFinite(count) || (count !== occurrences && !(run.validatedShards && occurrences > count && failures.length === count)) || (subruns && failures.length !== count) || (count > 0 && failures.length === 0)
|
|
1275
1323
|
|| (rc === 1 && count === 0 && errorCount === 0) || (rc === 0 && count + errorCount > 0)) {
|
|
1276
1324
|
throw incomplete(`failure attribution incomplete: summary=${count}, identified=${failures.length}`);
|
|
1277
1325
|
}
|
|
@@ -1298,7 +1346,7 @@ export function readIsolatedRun(run: CommandResult, label: string, file: string)
|
|
|
1298
1346
|
if (!cleanNonzero) {
|
|
1299
1347
|
const read = failuresOf(run, label);
|
|
1300
1348
|
// Two (fail) lines folding into one id would undercount the file's failures: unreadable, not fewer.
|
|
1301
|
-
const failCount = summaryCount(output, 'fail');
|
|
1349
|
+
const failCount = deterministicSubrunCounts(output)?.fail ?? summaryCount(output, 'fail');
|
|
1302
1350
|
if (Number.isFinite(failCount) && read.failures.length !== failCount)
|
|
1303
1351
|
throw new Error(`${label} incomplete (rc=${run.rc}; failure attribution summary=${failCount}, identified=${read.failures.length})`);
|
|
1304
1352
|
return { kind: 'read', ...read };
|
|
@@ -1419,7 +1467,40 @@ export interface GateAdmission {
|
|
|
1419
1467
|
/** The pool-wide reading admission waits on (recommended · limitedBy · memorySlots); null before any reading. */
|
|
1420
1468
|
waitReason(): string | null;
|
|
1421
1469
|
}
|
|
1422
|
-
export const GATE_ADMISSION_WAIT_SECONDS_DEFAULT =
|
|
1470
|
+
export const GATE_ADMISSION_WAIT_SECONDS_DEFAULT = 1800;
|
|
1471
|
+
/** Queue pressure scales the default wait but never creates an unbounded admission timer. */
|
|
1472
|
+
export function gateAdmissionWaitSeconds(shards: number, slots: number): number {
|
|
1473
|
+
return Math.max(GATE_ADMISSION_WAIT_SECONDS_DEFAULT, Math.min(3600,
|
|
1474
|
+
Math.ceil(shards / Math.max(1, slots)) * 300));
|
|
1475
|
+
}
|
|
1476
|
+
export function createGateRootLimiter(slots: number): <T>(run: () => Promise<T>) => Promise<T> {
|
|
1477
|
+
if (!Number.isSafeInteger(slots) || slots < 1) throw new Error('invalid gate root slots');
|
|
1478
|
+
let active = 0;
|
|
1479
|
+
const waiters: Array<() => void> = [];
|
|
1480
|
+
return async <T>(run: () => Promise<T>): Promise<T> => {
|
|
1481
|
+
if (active >= slots) await new Promise<void>((resolve) => waiters.push(resolve));
|
|
1482
|
+
else active++;
|
|
1483
|
+
try { return await run(); }
|
|
1484
|
+
finally {
|
|
1485
|
+
const next = waiters.shift();
|
|
1486
|
+
if (next) next(); // transfer the occupied slot before another caller can enter
|
|
1487
|
+
else active--;
|
|
1488
|
+
}
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
/** A direct gate has no preflight image pin. Its measured receipt still carries its own image digest. */
|
|
1492
|
+
export function gateReceiptReuseKey(receipt: GateShardReceipt | null, fingerprint: string, expectedImageDigest: string | null | undefined,
|
|
1493
|
+
hosts: ReadonlySet<string>): { envKey?: string; reason: string } {
|
|
1494
|
+
if (!receipt) return { reason: 'receipt-missing' };
|
|
1495
|
+
if (receipt.status !== 'complete') return { reason: 'receipt-unmeasured' };
|
|
1496
|
+
if (!hosts.has(receipt.host)) return { reason: 'receipt-host-outside-pool' };
|
|
1497
|
+
if (expectedImageDigest) return receipt.envKey === `${fingerprint}:${expectedImageDigest}`
|
|
1498
|
+
? { envKey: receipt.envKey, reason: 'image-pinned' } : { reason: 'receipt-image-or-env-drift' };
|
|
1499
|
+
const prefix = `${fingerprint}:`;
|
|
1500
|
+
if (!receipt.envKey.startsWith(prefix)) return { reason: 'receipt-env-drift' };
|
|
1501
|
+
return /^sha256:[a-f0-9]{64}$/i.test(receipt.envKey.slice(prefix.length))
|
|
1502
|
+
? { envKey: receipt.envKey, reason: 'measured-image' } : { reason: 'receipt-image-unmeasured' };
|
|
1503
|
+
}
|
|
1423
1504
|
export type GateAdmissionOutcome = 'granted' | 'admission-timeout' | 'admission-error' | 'off';
|
|
1424
1505
|
export interface GateAdmissionResult { outcome: GateAdmissionOutcome; waitedMs: number; reason?: string; release: () => void }
|
|
1425
1506
|
export const gateAdmissionWaitText = (reading: string | null) => `메모리 여유 대기(admission) · ${reading ?? '측정 전'}`.slice(0, 160);
|
|
@@ -1569,6 +1650,10 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1569
1650
|
const watchList = pod.riskWatch ? readGateRiskWatchList(pod.riskWatch) : [];
|
|
1570
1651
|
const riskPlan = planGateRiskLaneShards({ assignable, durations, shardCount, heavy, watchList });
|
|
1571
1652
|
const shards = riskPlan.shards;
|
|
1653
|
+
const receiptRoot = pod.shardsFile?.receiptRoot;
|
|
1654
|
+
const base40 = pod.shardsFile?.base40;
|
|
1655
|
+
const envFingerprint = pod.shardsFile?.envFingerprint;
|
|
1656
|
+
const expectedImageDigest = pod.shardsFile?.expectedImageDigest;
|
|
1572
1657
|
if (riskPlan.spawnFiles.length) debug.log('release-loop.gate', 'pod-spawn-lane', { files: riskPlan.spawnFiles, shards: riskPlan.spawnShardCount });
|
|
1573
1658
|
if (riskPlan.riskFiles.length) debug.log('release-loop.gate', 'pod-risk-lane-plan', { files: riskPlan.riskFiles, shard: 0,
|
|
1574
1659
|
plannedSeconds: riskPlan.riskPlannedSeconds, shards: shards.length, watch: watchList.length });
|
|
@@ -1590,6 +1675,56 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1590
1675
|
const ready = poolOverride ? undefined : checkPodPool(parsePodPool(pod.pool), kubectl);
|
|
1591
1676
|
if (ready && !ready.ok) throw new Error('pod command: 풀의 노드가 하나도 준비되지 않았다');
|
|
1592
1677
|
const poolScheduler = poolOverride ?? new PodPoolScheduler(ready!.ready);
|
|
1678
|
+
const receiptChecks = shards.map((item, shard) => {
|
|
1679
|
+
if (pod.freshShards) return { reason: 'fresh-shards' };
|
|
1680
|
+
if (!receiptRoot || !base40 || !envFingerprint) return { reason: 'receipt-context-missing' };
|
|
1681
|
+
const prior = readGateShardManifest(receiptRoot, `pod-${shard}`);
|
|
1682
|
+
const key = gateReceiptReuseKey(prior, envFingerprint, expectedImageDigest,
|
|
1683
|
+
new Set(poolScheduler.members.map((member) => member.context)));
|
|
1684
|
+
if (!key.envKey) return { reason: key.reason };
|
|
1685
|
+
const result = readGateShardResult(receiptRoot, { shard: { id: `pod-${shard}`, files: item.files },
|
|
1686
|
+
head40: commit, base40, envKey: key.envKey });
|
|
1687
|
+
return result ? { reason: key.reason, result } : { reason: 'receipt-identity-or-evidence-invalid' };
|
|
1688
|
+
});
|
|
1689
|
+
const receipts = receiptChecks.flatMap((check) => check.result ? [check.result.receipt] : []);
|
|
1690
|
+
const requiredOs = pod.shardCapabilities ?? {};
|
|
1691
|
+
const envKnownIds = loadEnvKnownFailures(tree);
|
|
1692
|
+
const envKnownFiles = new Set([...envKnownIds].map(fileOf));
|
|
1693
|
+
const macPassShardIds = shards.flatMap((shard, index) => {
|
|
1694
|
+
const prior = receipts.find((receipt) => receipt.id === `pod-${index}`);
|
|
1695
|
+
if (!prior) return [];
|
|
1696
|
+
const knownFailure = [...prior.failureIds, ...prior.errorIds].some((id) => envKnownIds.has(id));
|
|
1697
|
+
const knownUnmeasuredFile = prior.status === 'unmeasured' && shard.files.some((file) => envKnownFiles.has(file));
|
|
1698
|
+
return knownFailure || knownUnmeasuredFile ? [`pod-${index}`] : [];
|
|
1699
|
+
});
|
|
1700
|
+
for (const id of Object.keys(requiredOs)) if (!/^pod-\d+$/.test(id) || Number(id.slice(4)) >= shards.length)
|
|
1701
|
+
throw new Error(`unknown shard capability target: ${id}`);
|
|
1702
|
+
const needsPlatform = Object.values(requiredOs).some((value) => value.os)
|
|
1703
|
+
|| (pod.rerouteShard === 'auto' && macPassShardIds.length > 0);
|
|
1704
|
+
const hosts = poolScheduler.members.map((member) => {
|
|
1705
|
+
let platform: 'darwin' | 'linux' | 'other' = 'other';
|
|
1706
|
+
if (needsPlatform) {
|
|
1707
|
+
const system = member.sshHost ? remoteRun(member.sshHost, 'uname -s') :
|
|
1708
|
+
{ status: 0, stdout: process.platform, stderr: '' };
|
|
1709
|
+
if (system.status === 0) platform = /^(?:darwin)$/i.test(system.stdout.trim()) ? 'darwin'
|
|
1710
|
+
: /^(?:linux)$/i.test(system.stdout.trim()) ? 'linux' : 'other';
|
|
1711
|
+
}
|
|
1712
|
+
return { id: member.context, platform, available: true, reserved: false };
|
|
1713
|
+
});
|
|
1714
|
+
const hostPlan = planGateShardDispatch({ head40: commit, base40: base40 ?? commit,
|
|
1715
|
+
shards: shards.map((item, shard) => ({ id: `pod-${shard}`, files: item.files,
|
|
1716
|
+
...(requiredOs[`pod-${shard}`]?.os ? { requiredOs: requiredOs[`pod-${shard}`]!.os } : {}) })), receipts,
|
|
1717
|
+
hosts,
|
|
1718
|
+
macPassShardIds,
|
|
1719
|
+
...(pod.rerouteShard ? { rerouteShard: pod.rerouteShard } : {}), ...(pod.shardHost ? { shardHost: pod.shardHost } : {}) });
|
|
1720
|
+
// Leave ordinary shards to the live free-slot scheduler. A static plan can pick a busy member
|
|
1721
|
+
// while another is idle; pin only a capability or a deliberate cross-host replacement.
|
|
1722
|
+
const hostPins = new Map(hostPlan.flatMap((item) => item.kind === 'run'
|
|
1723
|
+
&& (requiredOs[item.shardId]?.os || (pod.rerouteShard === 'auto' && macPassShardIds.includes(item.shardId))
|
|
1724
|
+
|| (item.replacing && item.host !== item.replacing.host)
|
|
1725
|
+
|| (pod.shardHost && item.host === pod.shardHost)) ? [[item.shardId, item.host] as const] : []));
|
|
1726
|
+
const unavailable = hostPlan.find((item) => item.kind === 'unmeasured');
|
|
1727
|
+
if (unavailable) throw new Error(`gate shard ${unavailable.shardId} unmeasured: ${unavailable.reason}`);
|
|
1593
1728
|
const configCache = typeof pod.bunCache === 'string' ? pod.bunCache.trim() : '';
|
|
1594
1729
|
const envCache = process.env[POD_BUN_CACHE_HOST_PATH]?.trim();
|
|
1595
1730
|
const bunCache = configCache || envCache || undefined;
|
|
@@ -1600,11 +1735,13 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1600
1735
|
if (!Number.isSafeInteger(installSlots) || installSlots < 0) throw new Error('invalid pod sweep options');
|
|
1601
1736
|
debug.log('release-loop.gate', 'pod-install-slots', { slots: installSlots, source: pod.installSlots !== undefined ? 'config' : 'default' });
|
|
1602
1737
|
// GATE-MEM-ADMIT: every shard Job (retries and splits too) asks pool admission before it is placed.
|
|
1603
|
-
const
|
|
1738
|
+
const rootSlots = Math.max(1, poolScheduler.members.reduce((total, member) => total + member.capacity, 0));
|
|
1739
|
+
const admissionWaitSeconds = pod.admissionWaitSeconds ?? gateAdmissionWaitSeconds(shards.length, rootSlots);
|
|
1604
1740
|
if (!Number.isSafeInteger(admissionWaitSeconds) || admissionWaitSeconds < 0) throw new Error('invalid pod sweep options');
|
|
1605
1741
|
// An injected pool (tests) runs without admission unless `pod.admission` is passed; the gate's own pool asks it.
|
|
1606
1742
|
const gateAdmission = pod.admission ?? tools.admission ?? (poolOverride ? undefined : poolGateAdmission(poolScheduler));
|
|
1607
|
-
debug.log('release-loop.gate', 'pod-admission', { waitSeconds: admissionWaitSeconds, source: pod.admissionWaitSeconds !== undefined ? 'config' : 'default', enabled: !!gateAdmission && admissionWaitSeconds > 0
|
|
1743
|
+
debug.log('release-loop.gate', 'pod-admission', { waitSeconds: admissionWaitSeconds, source: pod.admissionWaitSeconds !== undefined ? 'config' : 'default', enabled: !!gateAdmission && admissionWaitSeconds > 0,
|
|
1744
|
+
rootSlots, shards: shards.length });
|
|
1608
1745
|
// GATE-POD-STALL-FAILFAST: a shard with no test output within the stall window is stopped and recorded unmeasured.
|
|
1609
1746
|
const stallSeconds = pod.stallSeconds ?? GATE_POD_STALL_SECONDS_DEFAULT;
|
|
1610
1747
|
const stallPollMs = pod.stallPollMs ?? GATE_POD_STALL_POLL_MS;
|
|
@@ -1722,29 +1859,88 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1722
1859
|
}, 0);
|
|
1723
1860
|
const knownTimes = assignable.map((file) => durations.get(file)).filter((value): value is number => value !== undefined && Number.isFinite(value) && value >= 0).sort((a, b) => a - b);
|
|
1724
1861
|
const unknownSeconds = knownTimes.length ? (knownTimes[Math.floor((knownTimes.length - 1) / 2)]! + knownTimes[Math.floor(knownTimes.length / 2)]!) / 2 : 1;
|
|
1725
|
-
// GATE-LIVE-OBS —
|
|
1726
|
-
//
|
|
1862
|
+
// GATE-LIVE-OBS — root rows stay stable; each recursive split/retry and tail gets its own row.
|
|
1863
|
+
// Otherwise a finished root Job can hide a still-running child (or tail) behind 80/80 done.
|
|
1727
1864
|
const board: GateShard[] = shards.map((item, index) => ({ id: `pod-${index}`, state: 'pending', plannedMin: Math.round(item.plannedSeconds / 6) / 10 }));
|
|
1865
|
+
const filesById = new Map(shards.map((item, index) => [`pod-${index}`, [...item.files]]));
|
|
1866
|
+
const detailsById = new Map<string, Partial<GateShardLedgerRow>>();
|
|
1867
|
+
const observeShard = (row: GateShard) => {
|
|
1868
|
+
if (!pod.shardsFile?.ledgerPath || !pod.shardsFile.envFingerprint) return;
|
|
1869
|
+
const detail = detailsById.get(row.id) ?? {};
|
|
1870
|
+
const logName = row.resultFile?.replace(/\.json$/, '') ?? null;
|
|
1871
|
+
const logPath = logName && logDir ? join(logDir, `${logName}.log`) : null;
|
|
1872
|
+
const junitPath = logName && logDir ? join(logDir, `${logName}.junit.xml`) : null;
|
|
1873
|
+
try {
|
|
1874
|
+
appendGateShardLedger(pod.shardsFile.ledgerPath, {
|
|
1875
|
+
v: 1, version: pod.shardsFile.version, phase: 'cut', shardId: row.id,
|
|
1876
|
+
files: filesById.get(row.id) ?? [], host: row.host ?? null, executor: 'pod',
|
|
1877
|
+
imageDigest: detail.imageDigest ?? null, commit, envFingerprint: pod.shardsFile.envFingerprint,
|
|
1878
|
+
startedAt: row.startedAt ?? null, endedAt: row.endedAt ?? null, observedAt: new Date().toISOString(),
|
|
1879
|
+
state: row.state, rc: row.rc ?? null, pass: detail.pass ?? null, fail: detail.fail ?? null,
|
|
1880
|
+
errors: detail.errors ?? null, ran: detail.ran ?? null,
|
|
1881
|
+
failureIds: detail.failureIds ?? null, errorIds: detail.errorIds ?? null,
|
|
1882
|
+
attribution: detail.attribution ?? 'unmeasured', logPath, junitPath,
|
|
1883
|
+
stopReason: detail.stopReason ?? row.waitReason ?? null, attemptId: row.attemptId ?? null,
|
|
1884
|
+
});
|
|
1885
|
+
} catch (error) { debug.log('release-loop.gate', 'shard-ledger-write-failed', { shard: row.id, error: String(error) }, { level: 'warn' }); }
|
|
1886
|
+
};
|
|
1728
1887
|
const flushBoard = () => {
|
|
1729
1888
|
if (!pod.shardsFile) return;
|
|
1730
1889
|
try { writeGateShards(pod.shardsFile.path, { v: 1, version: pod.shardsFile.version, updatedAt: new Date().toISOString(), shards: board }); }
|
|
1731
1890
|
catch (error) { debug.log('release-loop.gate', 'shards-write-failed', { path: pod.shardsFile.path, error: String(error) }); }
|
|
1732
1891
|
};
|
|
1733
|
-
const
|
|
1734
|
-
|
|
1892
|
+
const rowIndex = (shard: number, branch = '') => branch ? board.findIndex((row) => row.id === `pod-${shard}${branch}`) : shard;
|
|
1893
|
+
const markShard = (shard: number, patch: Partial<GateShard>, clear: Array<keyof GateShard> = [], branch = '') => {
|
|
1894
|
+
const index = rowIndex(shard, branch);
|
|
1895
|
+
const row = board[index];
|
|
1735
1896
|
if (!row) return;
|
|
1736
1897
|
const next: GateShard = { ...row, ...patch };
|
|
1737
1898
|
for (const key of clear) delete next[key];
|
|
1738
1899
|
if (JSON.stringify(next) === JSON.stringify(row)) return;
|
|
1739
|
-
board[
|
|
1900
|
+
board[index] = next;
|
|
1901
|
+
observeShard(next);
|
|
1740
1902
|
flushBoard();
|
|
1741
1903
|
};
|
|
1742
|
-
const resetShardAttempt = (shard: number) => {
|
|
1743
|
-
|
|
1904
|
+
const resetShardAttempt = (shard: number, branch = '', plannedMin?: number) => {
|
|
1905
|
+
if (branch && rowIndex(shard, branch) < 0) board.push({ id: `pod-${shard}${branch}`, state: 'pending', ...(plannedMin !== undefined ? { plannedMin } : {}) });
|
|
1906
|
+
const index = rowIndex(shard, branch);
|
|
1907
|
+
const row = board[index];
|
|
1744
1908
|
if (!row) return;
|
|
1745
|
-
board[
|
|
1909
|
+
board[index] = beginGateShardAttempt(row);
|
|
1910
|
+
delete board[index]!.rc;
|
|
1911
|
+
delete board[index]!.installSec;
|
|
1912
|
+
// The root is a logical aggregate while its descendants run, not the terminal state of its first Job.
|
|
1913
|
+
if (branch) {
|
|
1914
|
+
board[shard] = beginGateShardAttempt(board[shard]!);
|
|
1915
|
+
delete board[shard]!.rc;
|
|
1916
|
+
delete board[shard]!.installSec;
|
|
1917
|
+
observeShard(board[shard]!);
|
|
1918
|
+
}
|
|
1919
|
+
detailsById.delete(row.id);
|
|
1920
|
+
observeShard(board[index]!);
|
|
1746
1921
|
flushBoard();
|
|
1747
1922
|
};
|
|
1923
|
+
const recordShardResult = (id: string, done: { runs: ShardRun[]; stalled: NonNullable<GateResult['stalledShards']> }) => {
|
|
1924
|
+
const index = board.findIndex((row) => row.id === id);
|
|
1925
|
+
if (index < 0) return;
|
|
1926
|
+
const counts = done.runs.reduce((sum, run) => ({ pass: sum.pass + run.pass, fail: sum.fail + run.fail,
|
|
1927
|
+
errors: sum.errors + run.errors, ran: sum.ran + run.ran }), { pass: 0, fail: 0, errors: 0, ran: 0 });
|
|
1928
|
+
const failureIds: string[] = [];
|
|
1929
|
+
const errorIds: string[] = [];
|
|
1930
|
+
let attributed = done.stalled.length === 0 && done.runs.length > 0;
|
|
1931
|
+
for (const run of done.runs) {
|
|
1932
|
+
try {
|
|
1933
|
+
const parsed = failuresOf({ rc: run.rc, output: run.output, validatedShards: true }, `ledger ${id}`);
|
|
1934
|
+
failureIds.push(...parsed.failures); errorIds.push(...parsed.errors);
|
|
1935
|
+
} catch { attributed = false; }
|
|
1936
|
+
}
|
|
1937
|
+
const detail: Partial<GateShardLedgerRow> = { ...(detailsById.get(id) ?? {}), ...counts, failureIds: attributed ? [...new Set(failureIds)].sort() : null,
|
|
1938
|
+
errorIds: attributed ? [...new Set(errorIds)].sort() : null, attribution: attributed ? 'complete' : 'unmeasured',
|
|
1939
|
+
stopReason: done.stalled.map((item) => item.reason).join(', ') || null };
|
|
1940
|
+
detailsById.set(id, detail);
|
|
1941
|
+
observeShard(board[index]!);
|
|
1942
|
+
};
|
|
1943
|
+
for (const row of board) observeShard(row);
|
|
1748
1944
|
flushBoard();
|
|
1749
1945
|
// A result collector can lose its callback after the Kubernetes Job has already completed. Reconcile
|
|
1750
1946
|
// the public shard board independently so a finished Job cannot remain «running» indefinitely.
|
|
@@ -1758,6 +1954,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1758
1954
|
const index = board.findIndex((shard) => shard.id === item.shard);
|
|
1759
1955
|
if (index < 0) continue;
|
|
1760
1956
|
board[index] = reconciled.board.find((shard) => shard.id === item.shard)!;
|
|
1957
|
+
observeShard(board[index]!);
|
|
1761
1958
|
debug.log('release.gate', 'shard-stale-finalized', item);
|
|
1762
1959
|
}
|
|
1763
1960
|
flushBoard();
|
|
@@ -1768,22 +1965,25 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1768
1965
|
/** The gate's scheduler, seen through one shard: a slot wait and the Pod start become row states. */
|
|
1769
1966
|
// GATE-MEM-ADMIT: `placed` releases the shard's admission once a member is chosen — from then on the Job is
|
|
1770
1967
|
// Pending/Running in the cluster and the next admission reading counts it, so holding the lease would count it twice.
|
|
1771
|
-
const trackedPool = (shard: number,
|
|
1968
|
+
const trackedPool = (shard: number, branch: string, jobName: string, resultFile: string, attemptId: string, placed: (member: NonNullable<Awaited<ReturnType<PodPoolScheduler['tryAcquire']>>>) => void = () => {}, skipMembers: ReadonlySet<string> = new Set(), allowPin = false): PodPoolScheduler => ({
|
|
1772
1969
|
members: poolScheduler.members,
|
|
1773
1970
|
tryAcquire: async (...[skip, onEmpty, mode]: Parameters<PodPoolScheduler['tryAcquire']>) => {
|
|
1774
1971
|
// GATE-MULTI-HOST: a lost member is skipped while another member is left (all lost = the old behaviour).
|
|
1775
1972
|
const anyLeft = poolScheduler.members.some((m) => !downMembers.has(m.context));
|
|
1776
|
-
|
|
1973
|
+
// Only the first placement follows the plan; retries and unavailable hosts can move.
|
|
1974
|
+
const planned = allowPin && !skip?.size ? hostPins.get(`pod-${shard}`) : undefined;
|
|
1975
|
+
const pin = planned && !downMembers.has(planned) ? planned : undefined;
|
|
1976
|
+
const excluded = new Set([...(skip ?? []), ...skipMembers, ...(downMembers.size && anyLeft ? downMembers : []),
|
|
1977
|
+
...(pin ? poolScheduler.members.filter((m) => m.context !== pin).map((m) => m.context) : [])]);
|
|
1777
1978
|
const member = await poolScheduler.tryAcquire(excluded.size ? excluded : undefined, onEmpty, mode);
|
|
1778
1979
|
if (member) placed(member);
|
|
1779
|
-
if (member) markShard(shard, { state:
|
|
1780
|
-
else markShard(shard, { waitReason: 'Pod 자리 대기' });
|
|
1980
|
+
if (member) markShard(shard, { state: 'running', startedAt: board[rowIndex(shard, branch)]?.startedAt ?? new Date().toISOString(), host: member.context, job: jobName, resultFile, attemptId }, ['waitReason'], branch);
|
|
1981
|
+
else markShard(shard, { waitReason: 'Pod 자리 대기' }, [], branch);
|
|
1781
1982
|
return member;
|
|
1782
1983
|
},
|
|
1783
1984
|
release: (member: Parameters<PodPoolScheduler['release']>[0]) => poolScheduler.release(member),
|
|
1784
1985
|
}) as unknown as PodPoolScheduler;
|
|
1785
|
-
const
|
|
1786
|
-
if (branch || depth > 0 || parentAttempt !== undefined || hostRetries > 0) resetShardAttempt(shard);
|
|
1986
|
+
const runShardImpl = async (paths: string[], shard: number, depth = 0, branch = '', retriedNoOutput = false, fresh = pod.freshShards === true, parentAttempt?: string, hostRetries = 0, batchIsolation = false, skipMembers: ReadonlySet<string> = new Set(), singleShotTail = false): Promise<{ runs: ShardRun[]; stalled: NonNullable<GateResult['stalledShards']> }> => {
|
|
1787
1987
|
// 파일별 소요는 junit 으로 남긴다 — 콘솔 요약(판정 원천)은 그대로이고, 느린 시험 목록(K10 D4)·계층 분리(D2)의 자가 된다.
|
|
1788
1988
|
// GATE-TIMEOUT-HEAL: parts (each with its own junit) under a soft deadline inside this shard's own Job deadline.
|
|
1789
1989
|
const shardDeadline = shardDeadlineSeconds(batchIsolation ? Math.max(...paths.map((file) => estimated([file]))) * Math.ceil(paths.length / batchWidth) : estimated(paths), deadlineSeconds);
|
|
@@ -1847,13 +2047,13 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1847
2047
|
if (descent === 'retry') {
|
|
1848
2048
|
// «descend» is only the way down; «resumed» comes from each leaf whose log reads cleanly (review r4).
|
|
1849
2049
|
debug.log('release-loop.gate', 'pod-shard-descend', { shard, branch, files: paths.length, via: 'retry' });
|
|
1850
|
-
resetShardAttempt(shard);
|
|
2050
|
+
resetShardAttempt(shard, branch);
|
|
1851
2051
|
return runShard(paths, shard, depth, `${branch}-retry`, true, undefined, attemptId, hostRetries, batchIsolation, skipMembers);
|
|
1852
2052
|
}
|
|
1853
|
-
if (descent === 'split') {
|
|
1854
|
-
resetShardAttempt(shard);
|
|
2053
|
+
if (descent === 'split' || descent === 'file-fanout') {
|
|
2054
|
+
resetShardAttempt(shard, branch);
|
|
1855
2055
|
output = undefined;
|
|
1856
|
-
debug.log('release-loop.gate', 'pod-shard-descend', { shard, branch, files: paths.length, via:
|
|
2056
|
+
debug.log('release-loop.gate', 'pod-shard-descend', { shard, branch, files: paths.length, via: descent });
|
|
1857
2057
|
} else if (reused) {
|
|
1858
2058
|
output = reused.output;
|
|
1859
2059
|
junit = reused.junit;
|
|
@@ -1862,7 +2062,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1862
2062
|
lastFile = lastStartedTestFile(output, paths);
|
|
1863
2063
|
} else try {
|
|
1864
2064
|
stamp('admissionStart');
|
|
1865
|
-
admitted = await admitGateShard(gateAdmission, { waitMs: admissionWaitSeconds * 1000, onWait: (reason) => markShard(shard, { waitReason: reason }) });
|
|
2065
|
+
admitted = await admitGateShard(gateAdmission, { waitMs: admissionWaitSeconds * 1000, onWait: (reason) => markShard(shard, { waitReason: reason }, [], branch) });
|
|
1866
2066
|
stamp('admissionEnd');
|
|
1867
2067
|
if (admitted.outcome !== 'off') debug.log('release-loop.gate', 'shard-admission', { shard, branch, outcome: admitted.outcome, waitedMs: admitted.waitedMs, ...(admitted.reason ? { reason: admitted.reason } : {}) },
|
|
1868
2068
|
admitted.outcome === 'granted' ? undefined : { level: 'warn' });
|
|
@@ -1903,10 +2103,10 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1903
2103
|
graphNode: 'gate', version: pod.shardsFile?.version, commit, shard: `${shard + 1}-of-${shards.length}`,
|
|
1904
2104
|
reason: `release gate shard ${shard + 1}/${shards.length}`, pool: pod.pool, cpuReq: cpu.request,
|
|
1905
2105
|
memReq: memoryLimit ?? POD_ISOLATION_MEMORY_LIMIT, warmReused: false },
|
|
1906
|
-
pool: pod.pool, poolScheduler: trackedPool(shard,
|
|
2106
|
+
pool: pod.pool, poolScheduler: trackedPool(shard, branch, jobName, resultFile, attemptId, (member) => {
|
|
1907
2107
|
stamp('poolPlaced'); jobContext = member.context; jobContextForCapture = member.context; placedOn = member.context; placed();
|
|
1908
2108
|
if (branch === '-tail') tailPlacedHost = member.context;
|
|
1909
|
-
}, skipMembers), clone: true, source: { kind: 'commit', sha: commit }, deadlineSeconds: shardDeadline,
|
|
2109
|
+
}, skipMembers, depth === 0 && branch === '' && hostRetries === 0 && !retriedNoOutput && !batchIsolation && skipMembers.size === 0), clone: true, source: { kind: 'commit', sha: commit }, deadlineSeconds: shardDeadline,
|
|
1910
2110
|
...gateHostMirror(pod),
|
|
1911
2111
|
...(multiHost ? { statusUnreadableMs: memberUnreadableSeconds * 1000 } : {}),
|
|
1912
2112
|
...(bunCache ? { bunCache } : {}),
|
|
@@ -2029,6 +2229,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2029
2229
|
}
|
|
2030
2230
|
}
|
|
2031
2231
|
const installSeconds = parseInstallSeconds(output ?? '');
|
|
2232
|
+
if (imageDigest) detailsById.set(`pod-${shard}${branch}`, { ...(detailsById.get(`pod-${shard}${branch}`) ?? {}), imageDigest });
|
|
2032
2233
|
// GATE-TIMEOUT-HEAL ③: a cut shard is «reason: timeout» (with the files that still finished), not a bare rc=null.
|
|
2033
2234
|
if (!reused && !descent) debug.log('release-loop.gate', 'pod-shard', { shard, files: paths, durationMs, rc: rc ?? null, attempt: depth + 1, installSeconds, gateDeps: parseGateDeps(output ?? ''), deadlineSeconds: shardDeadline, parts: parts.length, ...(placedOn ? { host: placedOn } : {}),
|
|
2034
2235
|
...(testSeconds !== undefined ? { testSeconds } : {}),
|
|
@@ -2037,7 +2238,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2037
2238
|
// only for a Job that came back (a launch that failed before the Job returned has no Pod to describe).
|
|
2038
2239
|
if (!reused && !descent && podJobName !== undefined) emitPodLifecycle(gatePodLifecycleEvent({ runId: process.env.ELANOUS_RUN_ID ?? null, podJob: podJobName, host: placedOn ?? null,
|
|
2039
2240
|
shard: `pod-${shard}${branch}`, attemptId, phaseTimes, podStages, podLifecycle, outcome: gatePodOutcome({ jobFailed, stalled: !!stall, timedOut, rc }) }));
|
|
2040
|
-
if (!reused && !descent) markShard(shard, { ...(installSeconds !== null ? { installSec: installSeconds } : {}), ...(rc !== undefined ? { rc } : {}), ...(timedOut ? { state: 'timeout' as const } : {}) });
|
|
2241
|
+
if (!reused && !descent) markShard(shard, { ...(installSeconds !== null ? { installSec: installSeconds } : {}), ...(rc !== undefined ? { rc } : {}), ...(timedOut ? { state: 'timeout' as const } : {}) }, [], branch);
|
|
2041
2242
|
let clean = output?.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '');
|
|
2042
2243
|
const ran = clean && lastMatch(clean, /Ran (\d+) tests? across (\d+) files?/);
|
|
2043
2244
|
const passes = clean === undefined ? NaN : summaryCount(clean, 'pass');
|
|
@@ -2103,6 +2304,22 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2103
2304
|
// 137 (killed · OOM) keeps the isolation path: re-running an OOM file only re-measures the same kill.
|
|
2104
2305
|
const diedBeforeTests = reason === 'job-failed' && output === undefined && !jobFailed && jobExitCode !== undefined && jobExitCode !== 0
|
|
2105
2306
|
&& jobExitCode !== 137 && (paths.length === 1 || jobExitCode === 5);
|
|
2307
|
+
// A watched stall in a multi-file Job has already spent its first deadline. Measure each file on its own
|
|
2308
|
+
// under the sweep-wide isolation permit pool instead of paying for another whole-bundle Job and split tree.
|
|
2309
|
+
// The same descent is replayed after a cut resume when the parent and first child records are attributed.
|
|
2310
|
+
if (reason === 'no-output' && paths.length > 1 && !singleShotTail && (stall || descent === 'file-fanout')) {
|
|
2311
|
+
const count = Math.min(paths.length, isolationRemaining[shard]!);
|
|
2312
|
+
isolationRemaining[shard]! -= count;
|
|
2313
|
+
debug.log('release-loop.gate', 'pod-stall-file-fanout', { shard, branch, files: paths.length, launched: count,
|
|
2314
|
+
resumed: descent === 'file-fanout', limit: isolationConcurrency });
|
|
2315
|
+
const children = await Promise.all(paths.slice(0, count).map((file, index) => isolatedFile(() =>
|
|
2316
|
+
runShard([file], shard, depth + 1, `${branch}-file-${index}`, false, undefined, attemptId))));
|
|
2317
|
+
return { runs: children.flatMap((child) => child.runs), stalled: [
|
|
2318
|
+
...children.flatMap((child) => child.stalled),
|
|
2319
|
+
...(paths.length > count ? [{ shard, files: paths.slice(count), reason: 'no-output' as const,
|
|
2320
|
+
...(placedOn ? { lastHost: placedOn } : {}) }] : []),
|
|
2321
|
+
] };
|
|
2322
|
+
}
|
|
2106
2323
|
if ((reason === 'no-output' || diedBeforeTests) && !retriedNoOutput) {
|
|
2107
2324
|
const retry = await runShard(paths, shard, depth, `${branch}-retry`, true, undefined, attemptId, hostRetries, batchIsolation, skipMembers);
|
|
2108
2325
|
debug.log('release-loop.gate', 'pod-shard-retry', { shard, files: paths, reason, outcome: retry.stalled[0]?.reason ?? 'ok' });
|
|
@@ -2233,9 +2450,29 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2233
2450
|
errors: Number.isNaN(errors) ? 0 : errors,
|
|
2234
2451
|
ran: Number(ran![1]), files: Number(ran![2]), passedIds: junit ? junitPassedIds(junit) : [] }], stalled: [] };
|
|
2235
2452
|
};
|
|
2453
|
+
const runShard = async (...args: Parameters<typeof runShardImpl>): ReturnType<typeof runShardImpl> => {
|
|
2454
|
+
const [paths, shard, depth = 0, branch = '', , , parentAttempt, hostRetries = 0] = args;
|
|
2455
|
+
filesById.set(`pod-${shard}${branch}`, [...paths]);
|
|
2456
|
+
if (branch || depth > 0 || parentAttempt !== undefined || hostRetries > 0) {
|
|
2457
|
+
resetShardAttempt(shard, branch, Math.round(estimated(paths) / 6) / 10);
|
|
2458
|
+
}
|
|
2459
|
+
try {
|
|
2460
|
+
const done = await runShardImpl(...args);
|
|
2461
|
+
if (branch) markShard(shard, { state: !done.stalled.length ? 'done' : done.stalled.some((stall) => stall.reason === 'timeout') ? 'timeout' : 'failed', endedAt: new Date().toISOString() }, ['waitReason'], branch);
|
|
2462
|
+
if (branch) recordShardResult(`pod-${shard}${branch}`, done);
|
|
2463
|
+
return done;
|
|
2464
|
+
} catch (error) {
|
|
2465
|
+
if (branch) markShard(shard, { state: 'failed', endedAt: new Date().toISOString() }, ['waitReason'], branch);
|
|
2466
|
+
throw error;
|
|
2467
|
+
}
|
|
2468
|
+
};
|
|
2236
2469
|
// 모든 조각이 끝난 뒤 판정한다 — 한 조각의 예외로 먼저 돌아가면 다른 Pod 가 도는 채로 정리가 시작된다(#22002 리뷰 R3).
|
|
2237
2470
|
// GATE-RISK-LANE — the risk shard's own wall time (planned vs measured in its log line); it never changes a verdict.
|
|
2238
2471
|
const shardDurations: number[] = [];
|
|
2472
|
+
// Keep root shards outside the timed Pod admission until a gate slot is available.
|
|
2473
|
+
// Otherwise a wide sweep starts every admission clock together and later waves expire before placement.
|
|
2474
|
+
const withRootSlot = createGateRootLimiter(rootSlots);
|
|
2475
|
+
debug.log('release-loop.gate', 'pod-root-concurrency', { limit: rootSlots, shards: shards.length });
|
|
2239
2476
|
let settled: Array<PromiseSettledResult<Awaited<ReturnType<typeof runShard>>>>;
|
|
2240
2477
|
if (shardsFile) {
|
|
2241
2478
|
reconcileTimer = setInterval(reconcileTick, 30_000);
|
|
@@ -2245,10 +2482,59 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2245
2482
|
settled = await Promise.allSettled(shards.map(async (item, shard) => {
|
|
2246
2483
|
const shardStartedAt = clock();
|
|
2247
2484
|
try {
|
|
2248
|
-
const
|
|
2485
|
+
const shardId = `pod-${shard}`;
|
|
2486
|
+
const expected = hostPlan[shard]?.kind === 'reuse' ? receiptChecks[shard]?.result : null;
|
|
2487
|
+
if (expected) {
|
|
2488
|
+
const done = { runs: expected.runs, stalled: [] };
|
|
2489
|
+
shardDurations[shard] = 0;
|
|
2490
|
+
detailsById.set(shardId, { ...(detailsById.get(shardId) ?? {}), imageDigest: expected.receipt.envKey.slice(envFingerprint!.length + 1) });
|
|
2491
|
+
markShard(shard, { state: 'done', rc: done.runs.some((run) => run.fail || run.errors) ? 1 : 0,
|
|
2492
|
+
startedAt: expected.receipt.measuredAt, endedAt: new Date().toISOString(), host: expected.receipt.host }, ['waitReason']);
|
|
2493
|
+
recordShardResult(shardId, done);
|
|
2494
|
+
debug.log('release-loop.gate', 'shard-receipt-reused', { shard: shardId, host: expected.receipt.host, files: item.files.length });
|
|
2495
|
+
return done;
|
|
2496
|
+
}
|
|
2497
|
+
debug.log('release-loop.gate', 'shard-receipt-not-reused', { shard: shardId,
|
|
2498
|
+
reason: pod.rerouteShard === shardId ? 'reroute-requested'
|
|
2499
|
+
: receiptChecks[shard]?.result && pod.rerouteShard === 'auto' ? 'auto-reroute'
|
|
2500
|
+
: receiptChecks[shard]?.result ? 'dispatch-remeasure-required'
|
|
2501
|
+
: receiptChecks[shard]?.reason ?? 'receipt-unavailable',
|
|
2502
|
+
plan: hostPlan[shard]?.kind ?? 'none' });
|
|
2503
|
+
const receiptAttempt = receiptRoot ? nextGateShardAttempt(receiptRoot, shardId) : 1;
|
|
2504
|
+
if (receiptRoot && base40 && envFingerprint) {
|
|
2505
|
+
try {
|
|
2506
|
+
markGateShardUnmeasured(receiptRoot, { id: shardId, files: [...item.files], head40: commit, base40,
|
|
2507
|
+
host: 'pending', envKey: `${envFingerprint}:${expectedImageDigest ?? 'unmeasured'}`, status: 'unmeasured',
|
|
2508
|
+
failureIds: [], errorIds: [], evidencePath: 'pending', evidenceSha256: '0'.repeat(64),
|
|
2509
|
+
measuredAt: new Date().toISOString(), attempt: receiptAttempt });
|
|
2510
|
+
} catch (error) {
|
|
2511
|
+
debug.log('release-loop.gate', 'shard-receipt-invalidate-failed', { shard: shardId, error: String(error) }, { level: 'warn' });
|
|
2512
|
+
throw error;
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
// A missing or deliberately replaced receipt must execute a new Job. The older log-only resume
|
|
2516
|
+
// has no base/environment/image binding and cannot authorize this cross-host measurement.
|
|
2517
|
+
// The sixth runShardImpl argument is `fresh`: a missing receipt must launch a new Job.
|
|
2518
|
+
const freshForReceipt = !!receiptRoot || pod.freshShards === true;
|
|
2519
|
+
const done = await withRootSlot(() => runShard(item.files, shard, 0, '', false, freshForReceipt));
|
|
2249
2520
|
shardDurations[shard] = clock() - shardStartedAt;
|
|
2250
2521
|
const state = !done.stalled.length ? 'done' as const : done.stalled.some((stall) => stall.reason === 'timeout') ? 'timeout' as const : 'failed' as const;
|
|
2251
|
-
markShard(shard, { state, endedAt: new Date().toISOString() }, ['waitReason']);
|
|
2522
|
+
markShard(shard, { state, rc: done.stalled.length || done.runs.some((run) => run.fail || run.errors) ? 1 : 0, endedAt: new Date().toISOString() }, ['waitReason']);
|
|
2523
|
+
recordShardResult(`pod-${shard}`, done);
|
|
2524
|
+
if (receiptRoot && base40 && envFingerprint && !done.stalled.length) {
|
|
2525
|
+
const imageDigest = detailsById.get(shardId)?.imageDigest;
|
|
2526
|
+
const attribution = detailsById.get(shardId)?.attribution;
|
|
2527
|
+
const row = board[shard];
|
|
2528
|
+
if (imageDigest && row?.host && attribution === 'complete') {
|
|
2529
|
+
try {
|
|
2530
|
+
saveGateShardResult(receiptRoot, { receipt: { id: shardId, files: [...item.files], head40: commit, base40,
|
|
2531
|
+
host: row.host, envKey: `${envFingerprint}:${imageDigest}`, status: 'complete',
|
|
2532
|
+
failureIds: detailsById.get(shardId)?.failureIds ?? [], errorIds: detailsById.get(shardId)?.errorIds ?? [],
|
|
2533
|
+
evidencePath: 'pending', evidenceSha256: '0'.repeat(64), measuredAt: new Date().toISOString(),
|
|
2534
|
+
attempt: receiptAttempt }, runs: done.runs });
|
|
2535
|
+
} catch (error) { debug.log('release-loop.gate', 'shard-receipt-write-failed', { shard: shardId, error: String(error) }, { level: 'warn' }); }
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2252
2538
|
return done;
|
|
2253
2539
|
} catch (error) {
|
|
2254
2540
|
markShard(shard, { state: 'failed', endedAt: new Date().toISOString() }, ['waitReason']);
|
|
@@ -2274,6 +2560,8 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2274
2560
|
// GATE-STALLED-TAIL-REMEASURE: after the sweep, give only a one-file no-output leaf one fresh attempt on a different
|
|
2275
2561
|
// pool member. Keep the original unmeasured evidence if no alternate is available or the remeasure is incomplete.
|
|
2276
2562
|
const remainingStalls: typeof stalledShards = [];
|
|
2563
|
+
const tailTouched = new Set<number>();
|
|
2564
|
+
const tailRc = new Map<number, number>();
|
|
2277
2565
|
for (const stalled of stalledShards) {
|
|
2278
2566
|
const alternatives = stalled.lastHost
|
|
2279
2567
|
? poolScheduler.members.filter((member) => member.context !== stalled.lastHost && !downMembers.has(member.context))
|
|
@@ -2284,6 +2572,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2284
2572
|
}
|
|
2285
2573
|
debug.log('release-loop.gate', 'pod-stalled-tail-remeasure-start', { shard: stalled.shard, file: stalled.files[0], fromHost: stalled.lastHost,
|
|
2286
2574
|
availableHosts: alternatives.map((member) => member.context) });
|
|
2575
|
+
tailTouched.add(stalled.shard);
|
|
2287
2576
|
try {
|
|
2288
2577
|
tailPlacedHost = undefined;
|
|
2289
2578
|
const tail = await runShard(stalled.files, stalled.shard, 0, '-tail', true, true, undefined, 0, false, new Set([stalled.lastHost]), true);
|
|
@@ -2296,6 +2585,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2296
2585
|
fromHost: stalled.lastHost, toHost: unresolved.lastHost ?? null, reason: unresolved.reason }, { level: 'warn' });
|
|
2297
2586
|
} else {
|
|
2298
2587
|
measured.push(...tail.runs);
|
|
2588
|
+
tailRc.set(stalled.shard, tail.runs.some((run) => run.fail || run.errors) ? 1 : 0);
|
|
2299
2589
|
debug.log('release-loop.gate', 'pod-stalled-tail-remeasure-complete', { shard: stalled.shard, file: stalled.files[0],
|
|
2300
2590
|
fromHost: stalled.lastHost, toHost: tailPlacedHost ?? null });
|
|
2301
2591
|
}
|
|
@@ -2306,6 +2596,10 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2306
2596
|
}
|
|
2307
2597
|
}
|
|
2308
2598
|
stalledShards = remainingStalls;
|
|
2599
|
+
for (const shard of tailTouched) {
|
|
2600
|
+
const unresolved = stalledShards.some((item) => item.shard === shard);
|
|
2601
|
+
markShard(shard, { state: unresolved ? 'failed' : 'done', rc: unresolved ? 1 : tailRc.get(shard) ?? 0, endedAt: new Date().toISOString() }, ['waitReason']);
|
|
2602
|
+
}
|
|
2309
2603
|
const total = measured.reduce((sum, run) => ({ pass: sum.pass + run.pass, fail: sum.fail + run.fail,
|
|
2310
2604
|
errors: sum.errors + run.errors, ran: sum.ran + run.ran, files: sum.files + run.files }),
|
|
2311
2605
|
{ pass: 0, fail: 0, errors: 0, ran: 0, files: 0 });
|
|
@@ -2819,6 +3113,11 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
2819
3113
|
existsSync(join(root, 'release', opts.version, 'rehearsal', `${step}.json`)));
|
|
2820
3114
|
const currentImage = !explicitPartial && opts.graphRunId && opts.pod && rehearsalStampPresent
|
|
2821
3115
|
? imageProbe(opts.pod.pool) : null;
|
|
3116
|
+
const receiptImage = currentImage ?? (opts.graphRunId && opts.pod && process.env.ELANOUS_GRAPH_CONTEXT
|
|
3117
|
+
? imageProbe(opts.pod.pool) : null);
|
|
3118
|
+
const receiptBase = opts.baselineCommit ?? (opts.baselineVersion && roots.some((location) =>
|
|
3119
|
+
existsSync(join(location, 'release', opts.baselineVersion!, 'release.json')))
|
|
3120
|
+
? baselineReleaseCommit(opts.baselineVersion) : undefined);
|
|
2822
3121
|
const radius = !explicitPartial && opts.graphRunId && opts.pod && rehearsalStampPresent
|
|
2823
3122
|
? planHardRehearsalRadius({ root, repo, version: opts.version, forCommit: opts.commit,
|
|
2824
3123
|
graphPins: gateGraphPins(root, opts.graphRunId), envFingerprint: gateEnvFingerprint(process.env), imageDigest: currentImage })
|
|
@@ -2879,8 +3178,12 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
2879
3178
|
// the cut sweep puts those files first, the baseline sweep keeps its plan.
|
|
2880
3179
|
riskWatch: { instanceRoots: roots, ledgerRoots: roots },
|
|
2881
3180
|
// GATE-LIVE-OBS: the release ledger's shards.json (UX #24782) — never the run tree's universe; an explicit
|
|
2882
|
-
// --ledger-root
|
|
2883
|
-
shardsFile: { path:
|
|
3181
|
+
// --ledger-root or an explicit test instance keeps it in that ledger.
|
|
3182
|
+
shardsFile: { path: gateShardsPath(opts.version, ledger), version: opts.version,
|
|
3183
|
+
ledgerPath: gateShardLedgerPath(opts.version, ledger), envFingerprint: gateEnvFingerprint(process.env),
|
|
3184
|
+
receiptRoot: join(ledger, 'release', opts.version, 'gate-logs', 'cut', 'receipts'),
|
|
3185
|
+
base40: receiptBase,
|
|
3186
|
+
expectedImageDigest: receiptImage } } : undefined,
|
|
2884
3187
|
selected?.files);
|
|
2885
3188
|
let cutRun = await runCut(partial);
|
|
2886
3189
|
const radiusSelectionMatched = partial?.files && cutRun?.podSweep?.files
|
|
@@ -3418,7 +3721,7 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
3418
3721
|
// GATE-LIVE-OBS-RC-WORDING ⓒ — the shards board says «판정 전» until this lands; then «새 실패 N» only (UX · gate-shards.ts).
|
|
3419
3722
|
// Same ledger as the board's own writes (sweep's shardsFile). A write failure is logged and never changes the verdict.
|
|
3420
3723
|
if (result.outcome !== 'error' && opts.pod) {
|
|
3421
|
-
const path =
|
|
3724
|
+
const path = gateShardsPath(opts.version, ledger);
|
|
3422
3725
|
try {
|
|
3423
3726
|
const written = recordGateShardsVerdict(path, { introduced: result.introduced.length, preexisting: result.preexisting });
|
|
3424
3727
|
debug.log('release-loop.gate', 'shards-verdict', { version: opts.version, path, written, introduced: result.introduced.length, preexisting: result.preexisting });
|
|
@@ -3483,6 +3786,12 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
|
|
|
3483
3786
|
if (fromGraph.gateExecutor !== undefined && !isGateExecutor(fromGraph.gateExecutor)) throw new Error(`gateExecutor must be pod or host: ${String(fromGraph.gateExecutor)}`);
|
|
3484
3787
|
// GATE-HOST-EXECUTOR: a host gate never reads the pod settings (a bad pod value must not block the fallback executor).
|
|
3485
3788
|
const hostExecutor = fromGraph.gateExecutor === 'host';
|
|
3789
|
+
const graphCapabilities = fromGraph.gateShardCapabilities;
|
|
3790
|
+
if (!hostExecutor && graphCapabilities !== undefined && (!graphCapabilities || typeof graphCapabilities !== 'object'
|
|
3791
|
+
|| Array.isArray(graphCapabilities) || Object.entries(graphCapabilities).some(([id, value]) =>
|
|
3792
|
+
!/^pod-\d+$/.test(id) || !value || typeof value !== 'object' || Array.isArray(value)
|
|
3793
|
+
|| !['darwin', 'linux'].includes((value as { os?: string }).os ?? ''))))
|
|
3794
|
+
throw new Error('gateShardCapabilities must map pod-N to {os: darwin|linux}');
|
|
3486
3795
|
const opts: GateOptions = {
|
|
3487
3796
|
rehearsalTui: fromGraph.rehearsalTui === true,
|
|
3488
3797
|
recheckFileLimit: fromGraph.gateRecheckFileLimit !== undefined ? Number(fromGraph.gateRecheckFileLimit) : undefined,
|
|
@@ -3504,11 +3813,16 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
|
|
|
3504
3813
|
...(fromGraph.gatePodStallSeconds !== undefined ? { stallSeconds: Number(fromGraph.gatePodStallSeconds) } : {}),
|
|
3505
3814
|
...(fromGraph.gatePodMidRunStallSeconds !== undefined ? { midRunStallSeconds: Number(fromGraph.gatePodMidRunStallSeconds) } : {}),
|
|
3506
3815
|
...(fromGraph.gatePodFreshShards === true ? { freshShards: true } : {}),
|
|
3816
|
+
...(typeof fromGraph.gateRerouteShard === 'string' ? { rerouteShard: fromGraph.gateRerouteShard } : {}),
|
|
3817
|
+
...(typeof fromGraph.gateShardHost === 'string' ? { shardHost: fromGraph.gateShardHost } : {}),
|
|
3818
|
+
...(graphCapabilities ? { shardCapabilities: graphCapabilities as Record<string, { os: 'darwin' | 'linux' }> } : {}),
|
|
3507
3819
|
...((cpu) => cpu ? { cpu } : {})(parsePodCpu(fromGraph.gatePodCpu, 'release.loop.gatePodCpu')),
|
|
3508
3820
|
} : undefined,
|
|
3509
3821
|
};
|
|
3510
3822
|
if (isGateExecutor(fromGraph.gateExecutor)) opts.executor = fromGraph.gateExecutor;
|
|
3511
3823
|
let freshShards = false;
|
|
3824
|
+
let rerouteShard: string | undefined;
|
|
3825
|
+
let shardHost: string | undefined;
|
|
3512
3826
|
for (let i = 0; i < args.length; i++) {
|
|
3513
3827
|
const arg = args[i];
|
|
3514
3828
|
if (arg === '--help') return 'help';
|
|
@@ -3516,6 +3830,18 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
|
|
|
3516
3830
|
if (arg === '--rehearsal-baseline') { opts.rehearsalBaseline = true; continue; }
|
|
3517
3831
|
// Applied after the loop and only to a Pod gate — on its own it must not turn a local gate into a Pod one (review r5).
|
|
3518
3832
|
if (arg === '--fresh-shards') { freshShards = true; continue; }
|
|
3833
|
+
if (arg === '--reroute-shard') {
|
|
3834
|
+
const value = args[++i];
|
|
3835
|
+
if (!value || !/^(?:auto|pod-\d+)$/.test(value)) throw new Error('--reroute-shard requires auto or pod-N');
|
|
3836
|
+
rerouteShard = value;
|
|
3837
|
+
continue;
|
|
3838
|
+
}
|
|
3839
|
+
if (arg === '--shard-host') {
|
|
3840
|
+
const value = args[++i];
|
|
3841
|
+
if (!value || value.startsWith('--')) throw new Error('--shard-host requires a pool context');
|
|
3842
|
+
shardHost = value;
|
|
3843
|
+
continue;
|
|
3844
|
+
}
|
|
3519
3845
|
if (arg === '--rehearsal-tui') { opts.rehearsalTui = true; continue; }
|
|
3520
3846
|
if (arg === '--recheck-file-limit') {
|
|
3521
3847
|
const value = args[++i];
|
|
@@ -3544,6 +3870,11 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
|
|
|
3544
3870
|
else if (key === 'remoteMirror') opts.remoteMirror = value;
|
|
3545
3871
|
}
|
|
3546
3872
|
if (freshShards && opts.pod) opts.pod = { ...opts.pod, freshShards: true };
|
|
3873
|
+
if ((rerouteShard || shardHost) && !opts.pod?.pool) throw new Error('shard reroute requires a Pod pool');
|
|
3874
|
+
if (shardHost && !rerouteShard) throw new Error('--shard-host requires --reroute-shard');
|
|
3875
|
+
if (opts.pod && rerouteShard) opts.pod = { ...opts.pod, rerouteShard, ...(shardHost ? { shardHost } : {}) };
|
|
3876
|
+
if (opts.pod?.rerouteShard && !/^(?:auto|pod-\d+)$/.test(opts.pod.rerouteShard)) throw new Error('gateRerouteShard must be auto or pod-N');
|
|
3877
|
+
if (opts.pod?.shardHost && !opts.pod.rerouteShard) throw new Error('gateShardHost requires gateRerouteShard');
|
|
3547
3878
|
// GATE-HOST-EXECUTOR: the host executor is the existing non-pod sweep on gateRemote — a configured pool must not pull it
|
|
3548
3879
|
// back into Pods, and without a host it would silently sweep on the runner's own machine.
|
|
3549
3880
|
if (opts.executor === 'host') {
|
|
@@ -3576,7 +3907,7 @@ export async function runGateNode(argv: string[], env: NodeJS.ProcessEnv, runner
|
|
|
3576
3907
|
debug.log('release-loop.gate', 'executor', { version: opts.version, ...placement, requested: opts.executor ?? null });
|
|
3577
3908
|
}
|
|
3578
3909
|
if (opts === 'help') {
|
|
3579
|
-
console.log('Usage: bun scripts/release-loop/gate-node.ts --commit <sha> --version <v> [--baseline-version <prev>] [--baseline-commit <sha>] [--remote <ssh-host>] [--remote-mirror <path>] [--pod-pool <pool>] [--pod-shards <n>] [--pod-shard-timeout-seconds <n>] [--recheck-file-limit <n>] [--fresh-shards] [--executor <pod|host>] [--rehearsal-baseline] [--json]\nWithout flags, input.commit, input.version and input.previousVersion come from the JSON file at ELANOUS_GRAPH_CONTEXT.\n--executor host (input.gateExecutor) ignores every pod option and sweeps on --remote (input.gateRemote, required).\n--rehearsal-baseline pins source graph bytes only for a direct Pod gate on an isolated rc version.');
|
|
3910
|
+
console.log('Usage: bun scripts/release-loop/gate-node.ts --commit <sha> --version <v> [--baseline-version <prev>] [--baseline-commit <sha>] [--remote <ssh-host>] [--remote-mirror <path>] [--pod-pool <pool>] [--pod-shards <n>] [--pod-shard-timeout-seconds <n>] [--recheck-file-limit <n>] [--fresh-shards] [--reroute-shard <pod-N|auto>] [--shard-host <pool-context>] [--executor <pod|host>] [--rehearsal-baseline] [--json]\nWithout flags, input.commit, input.version and input.previousVersion come from the JSON file at ELANOUS_GRAPH_CONTEXT.\n--executor host (input.gateExecutor) ignores every pod option and sweeps on --remote (input.gateRemote, required).\n--rehearsal-baseline pins source graph bytes only for a direct Pod gate on an isolated rc version.');
|
|
3580
3911
|
return 0;
|
|
3581
3912
|
}
|
|
3582
3913
|
version = opts.version;
|