elanous 0.2.40 → 0.2.42
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/README.md +7 -7
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/{HjeK7zONHfR7op1hKXp4k → X1ucLw10Cds_h614NoEnh}/_buildManifest.js +1 -1
- package/apps/pwa/out/_next/static/chunks/0244e5b5.6fc7670f8223d82d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{4769.9fd582071301f382.js → 1063.2fb8a9dc55c731f2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1121.13ba7a8aff19df81.js +36 -0
- package/apps/pwa/out/_next/static/chunks/1147-5196d19c222051b3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{470.51a72a2348306455.js → 1262.e804feb8fd1acb8c.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/1319.b9b815ae0f181e06.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1345-31427b833438f521.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7219-15bdd9a80661af8e.js → 1455-530ca16c7e71baff.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{1281.d81877e35b9068f4.js → 1487.2f67b79444062da6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1574.878c18ecc97b1bd4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1586.fafc33a9a5835cb7.js +32 -0
- package/apps/pwa/out/_next/static/chunks/1744.855fd60ae4c4a8c8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/193.21a47a3e07ef6860.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1945.7b54990d578c119b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2004.e839fda7ea5a738a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2044-dd681a5c08779ff8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2101.2248decb38829b03.js +286 -0
- package/apps/pwa/out/_next/static/chunks/2160.a097e7b138ae1c55.js +23 -0
- package/apps/pwa/out/_next/static/chunks/2300.41ea70ac1bef1446.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2357-07b5eeb5fb12628d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2364.28652020695a6db3.js +34 -0
- package/apps/pwa/out/_next/static/chunks/{1350.59f40e39e02d91b1.js → 2456.b6de0e1c05f28843.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2526.c3e50ac1fec064bd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2549-cda50b716ff4250c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2550-5f1e88f615568045.js +1 -0
- package/apps/pwa/out/_next/static/chunks/261-319078c954e40d66.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2690.0521c738b7749931.js +115 -0
- package/apps/pwa/out/_next/static/chunks/{5436.fdf6fa8ae3e9588d.js → 2974.595d1e29d040cb6a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{5383.bdfa5d886e39a826.js → 3207.a756c95e9ec8c3c8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3285.89ee2f298682f5e5.js +93 -0
- package/apps/pwa/out/_next/static/chunks/{2177.df8547f9d7215c4e.js → 3347.f846266b5e5f3ede.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/3447.06edc7b092021417.js +41 -0
- package/apps/pwa/out/_next/static/chunks/{9184.45afbf44fec87574.js → 3482.a9ae9d2ad6a5bd97.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3507.97e1977bc4d5fb0a.js +2 -0
- package/apps/pwa/out/_next/static/chunks/3510-9ca392698bfe4391.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3541.772e5ce425aaa611.js +24 -0
- package/apps/pwa/out/_next/static/chunks/{8099.1cccf31c1baa7e1c.js → 3741.9cf76acaa2ee5298.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3837.a210d7d0db8d8abd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3898-955c896f394217a6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3947-ef57acea76eaa8d2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{4864a2ca.1c1c280a7772c5e5.js → 3d0154af.5331fec737ffe8c3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4037-3b82ebc7d78afdc7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4043.6b91203436c9c86b.js +309 -0
- package/apps/pwa/out/_next/static/chunks/405.9022d4a478202b67.js +77 -0
- package/apps/pwa/out/_next/static/chunks/417-fb2cc9a9910e5968.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3330.7443183b7e6fdbdf.js → 4276.336d76d858fcdf1b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4302.6ced9aa4535de947.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4320.583cbab64db6be02.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4768-8eac183fd291ca53.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4770.769c9cd9c7034eba.js +148 -0
- package/apps/pwa/out/_next/static/chunks/4895.65605ec3e6504e89.js +74 -0
- package/apps/pwa/out/_next/static/chunks/{3b355b3d.6f73c98bf6f9c749.js → 50061469.0326382241ef3b2d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5112.4b9f300124fab7b3.js +149 -0
- package/apps/pwa/out/_next/static/chunks/5335.375ee922bcd5bc83.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5342.023a2e729689b320.js +27 -0
- package/apps/pwa/out/_next/static/chunks/{8d173e38.18a2f7b4e1848bdb.js → 53c73f84.373b6160adfa89a4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{1537-6bb97b5214928ea9.js → 5431-72a1dbde59400691.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5440-4236de26c5ea5856.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5486-b318c165f645d8ba.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5506-c7d2ec25c62a7c94.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5570-c66ecbea26b4e3b1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5696.ffca5aa259d8451e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5793-8c584aea2aa87560.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5848-6de6b8c173351a90.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5885.0a3e52c8faccec98.js +287 -0
- package/apps/pwa/out/_next/static/chunks/5999.94e3c83040200f31.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5a7901d6-2d8d9b5587cf9042.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{261.0d471e8aded799f3.js → 6131.348b7d6bdcd267ce.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/6164.86133a0172611184.js +90 -0
- package/apps/pwa/out/_next/static/chunks/{9175.2de5209a5ce6f68a.js → 6377.bc7df239edbff244.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6515.e07e8b2b8a707a87.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6522-c005514f87d18982.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2226.2cf61ec781827219.js → 6632.0eac1c554f8f2ef3.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/6642.648adb1767f39ecf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6848.138805a09d001d09.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7049-c09e2b7a253a24f3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7072.1e00447d9fa39972.js +104 -0
- package/apps/pwa/out/_next/static/chunks/7106.64201c539c5e220c.js +94 -0
- package/apps/pwa/out/_next/static/chunks/7151-1f742b455986530e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7199.18d6b25e001154ce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7279-c199ea83f34338fa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/728b938d.ffaa7ad583d60e58.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7309.171c3de54e0322f1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/733.7a1ab3342e03306f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{b84cad00.594027ebe0531fe2.js → 73d2eb0e.4966ad55293c801a.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/{6257.c0037b8949239f5e.js → 7411.9b4b8b747dc7b802.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/7504.d5cf1e7cd89b1b08.js +24 -0
- package/apps/pwa/out/_next/static/chunks/7573-0fd4af62d65d4598.js +1 -0
- package/apps/pwa/out/_next/static/chunks/759.6f8267df4689c401.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7825-668b046f72f14483.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7852-1538768b6eb19388.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7868.cff810430bb63c91.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9189505a.980427ac048c8c60.js → 7a98bc80.fa935989e829f9c2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8084-dfcc796874ab4e07.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8138.6c10b2a356d52fca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2566.beaffc3a5e1fcd2e.js → 8262.ddc03e06b733f184.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8266.6bf9b131b2584678.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8403.7b81d5e64074a725.js +8 -0
- package/apps/pwa/out/_next/static/chunks/8470.1998cbd0866a7eac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8505.fd803c6c33b54cc3.js +79 -0
- package/apps/pwa/out/_next/static/chunks/{8272-025653884f9645d2.js → 8546-6f03f8ea34dd61d9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{6051-0ae955eedb10adb9.js → 8673-bab689a55f7e13b9.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/8687-246fda8433d3477c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/898-280022fb082936bc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9098-50eefc1844212e3d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9211-330b6736a63d4cb3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{4351.0f37a6db2b34d50b.js → 9249.976f1e69a9d80754.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9255-cf88fceda5666645.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9306-e523ba58bb8adc42.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9324.a9866d717ba62cb6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9325.a8510e8aa87c8143.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9353-12bfff0c130104cf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9415.69e4a21c60c7d2db.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6749.0744ce6eb08d40e9.js → 9427.96aa912ddb5801e0.js} +16 -16
- package/apps/pwa/out/_next/static/chunks/9479-154a697d4b6e4e98.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9540-65aa6ccc31b3fd78.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7611.85f045bf232053ac.js → 9633.ce5bddc56784da72.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{3746-f80a78f220bf37b5.js → 9656-374232558fd528d0.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9832.f4a98c878ecf2c0a.js +62 -0
- package/apps/pwa/out/_next/static/chunks/9865.f094440645f02d98.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9a852a62-6192a6ea9346ced3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-e772b4710df5d936.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-4361368e6a76b967.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-802f349fdd65248d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-7f35513988a2a495.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-798121656540e33e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-20a089490affe98c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-e5e4ec786d70e604.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-d12fe5d0801430e4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-d8cf21022fecc026.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-21ce565c9c57ee97.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-b56563025f619c9b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-52f14dd133da8348.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-c0ed7fdcdba76633.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-af8c3020cb520f15.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-72eda67af9f5e35c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-e772b4710df5d936.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-67a6620d31f0a46f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-f059363aa0880ec2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-f9f917d6a57f69fb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-d3ae55eee2f0826c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/{page-83d27757e2b8060a.js → page-0e3d7b9421b79dac.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-ba738f7e233eb211.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-33438b98c74fd599.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-0b34d5fd990d793a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-0f291d3c8fd55a3a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-3fced192be5279c1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-04cb3cf182a7c1ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-6ccbd2ab23a21542.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-aa0518c48a5f4e56.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-4b32226d341ad7d7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-3224fd8f6383b61f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-efbc97459f2914e2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-2fe487d88cd5d97c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-48861dd1a858bab7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-197d5d4d21d228ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a763ba89fbdd9c27.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-7edb253510b1c136.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-0c875702c61965ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-e772b4710df5d936.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-e425c951151095f7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-9c90e0aec4e27f77.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-733e09a02e727d42.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-4f899e14c34d77a1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/{page-139d722de1dcd6c1.js → page-bf067887b3307423.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-7af10868ff87d04e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-efe5304057aa11c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-76bcd29aaa8a26c3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-4cfd88e2273ce50f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-0a1ddb47efba0a32.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2a176c707152c892.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-efe03031b8797fbf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{409c787b.e48c5bec8ec10456.js → b450776b.3db34d8d965f82d3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{ca3702f8.2913182e73889fa2.js → e8543907.5b040a5f22a0b0e5.js} +7 -7
- package/apps/pwa/out/_next/static/chunks/framework-d0d2d673bfd8a8a9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-4c9fceb2baa09cb7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-b0ab8d8fc9a97556.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-7e2cc62a2e5cec96.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-22324935f753ec5e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-9dc1bfa25334998a.js +1 -0
- package/apps/pwa/out/_next/static/css/{b9d8151e48998417.css → 62c858ad53854f47.css} +1 -1
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +22 -22
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +22 -22
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +22 -22
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +21 -21
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +21 -21
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +22 -22
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +21 -21
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +22 -22
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +22 -22
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +21 -21
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +22 -22
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +23 -24
- 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 +22 -22
- 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 +23 -24
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +23 -24
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +21 -21
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +21 -21
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +21 -21
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +22 -22
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +22 -22
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +21 -21
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +21 -21
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +21 -21
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +21 -21
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +22 -22
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +22 -22
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +21 -21
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +22 -22
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +22 -22
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +22 -22
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +22 -22
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +22 -22
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +21 -21
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +22 -22
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +22 -22
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +22 -22
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +22 -22
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +22 -22
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +22 -22
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +22 -22
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +22 -22
- package/bin/elanous.mjs +6 -1
- package/catalog/models/openai/gpt-6.1-sol.yaml +16 -0
- package/catalog/models/openai/gpt-6.1-terra.yaml +16 -0
- package/docs/goal-context/04-mock-scenarios.md +13 -0
- package/graphs/catalog/catalog.yaml +1 -1
- package/graphs/demo/persona-teacher/lesson-pack.yaml +16 -0
- package/graphs/demo/persona-teacher/recipes.yaml +17 -0
- package/graphs/demo/persona-teacher/weekly-lesson.yaml +36 -0
- package/graphs/heal/heal-loop.yaml +1 -0
- package/graphs/overlays/stop-heal-ladder.yaml +136 -2
- package/graphs/release/release-loop.yaml +3 -1
- package/graphs/units/gate/gate.yaml +20 -0
- package/graphs/units/git/git.yaml +26 -0
- package/graphs/units/implement/implement.yaml +19 -0
- package/graphs/units/landing/landing.yaml +18 -0
- package/graphs/units/review/review.yaml +20 -0
- package/graphs/units/verbs/recipes.yaml +13 -10
- package/graphs/units/verbs/research.yaml +20 -0
- package/graphs/work/research-report.yaml +21 -3
- package/package.json +1 -1
- package/scripts/disk-steward.ts +46 -0
- package/scripts/fleet/node-setup.sh +50 -0
- package/scripts/graph-units/verbs/research.ts +55 -0
- package/scripts/heal/run-role.ts +99 -5
- package/scripts/land-global-checks.ts +6 -1
- package/scripts/lib/decompose-ab.ts +1 -0
- package/scripts/lib/fixtures/tui-regress/help-insufficient-commands.txt +15 -0
- package/scripts/lib/fixtures/tui-regress/help-live.txt +42 -0
- package/scripts/lib/fixtures/tui-regress/help-missing-keys.txt +21 -0
- package/scripts/lib/fixtures/tui-regress/help-original.txt +51 -0
- package/scripts/lib/fixtures/tui-regress/help-shifted-input.txt +26 -0
- package/scripts/lib/tui-regress-checks.ts +33 -13
- package/scripts/llm-cost-ledger.ts +179 -8
- package/scripts/measure-nl-routing-corpus.ts +10 -2
- package/scripts/persona-teacher/weekly-lesson-node.ts +172 -0
- package/scripts/public-copy-lint.ts +163 -19
- package/scripts/ratchets/train-side-invariants.sh +0 -1
- package/scripts/release-loop/gate-autoheal.ts +117 -0
- package/scripts/release-loop/gate-baseline-receipt.ts +55 -10
- package/scripts/release-loop/gate-diff.ts +22 -3
- package/scripts/release-loop/gate-image-pin.ts +81 -0
- package/scripts/release-loop/gate-node-sim.ts +207 -0
- package/scripts/release-loop/gate-node.ts +489 -62
- package/scripts/release-loop/gate-pod-image-sticky-plan.ts +99 -0
- package/scripts/release-loop/gate-radius-live.ts +97 -13
- package/scripts/release-loop/gate-shard-dispatch.ts +43 -0
- package/scripts/release-loop/gate-shard-load-flaky.ts +256 -0
- package/scripts/release-loop/gate-train-receipts.ts +122 -0
- package/scripts/release-loop/gate-triage-node.ts +70 -10
- package/scripts/release-loop/resume-release.ts +330 -23
- package/scripts/release-loop/traffic-node.ts +6 -3
- package/scripts/release-loop/unattended-release.ts +4 -2
- package/scripts/shadow-authoring-probe.ts +20 -0
- package/scripts/ux-sim.ts +37 -3
- package/scripts/warm-pool-provider-config.ts +263 -0
- package/skills/omni-crawl/SKILL.md +5 -2
- package/skills/omni-crawl/scripts/main.ts +84 -23
- package/src/acp/core-turn-bridge.ts +99 -4
- package/src/agent/registry.ts +2 -0
- package/src/agent/task-notification.ts +25 -5
- package/src/agent/types.ts +6 -0
- package/src/agent-mission/mission-cli.ts +24 -0
- package/src/agent-mission/mission-out.ts +595 -0
- package/src/agent-mission/tool-install.ts +88 -2
- package/src/agent-substrate/context-strategy.ts +4 -4
- package/src/agent-substrate/self-review-cli.ts +267 -40
- package/src/auto-research/research-turn.ts +133 -51
- package/src/autopilot/discovery/wiki-claim-proposer.ts +22 -1
- package/src/autopilot/pr-manager.ts +38 -7
- package/src/autopilot/pty-control-loop.ts +2 -0
- package/src/boot/daemon-tools/self-implement.ts +46 -9
- package/src/budget/shadow-arms.ts +52 -0
- package/src/capture/tui-self-report.ts +22 -6
- package/src/chat/index.ts +1 -1
- package/src/chat/pickers/state.ts +7 -1
- package/src/cli/fixtures/review-loop-breaker/opwaiver-32646-1694ccc7a170.json +1 -0
- package/src/cli/fixtures/review-loop-breaker/opwaiver-32853-bd8f73595c83.json +1 -0
- package/src/cli/fixtures/review-loop-breaker/opwaiver-33097-1cc85900413c.json +1 -0
- package/src/cli/fixtures/review-loop-breaker/opwaiver-33106-972f31e4a3ca.json +1 -0
- package/src/cli/fixtures/review-loop-breaker/opwaiver-33113-3ab51f05968f.json +1 -0
- package/src/cli/fixtures/review-loop-breaker/pr-33097-1cc85900413c.json +1 -0
- package/src/cli/log-event-names.ts +5 -0
- package/src/cli/mcp-login.ts +8 -18
- package/src/cli/msg-cli.ts +57 -1
- package/src/cli/pr-land-heavy-remote.ts +47 -9
- package/src/cli/pr-land-overlap-evidence.ts +100 -1
- package/src/cli/pr-land-pod-shadow-job.ts +60 -0
- package/src/cli/release-checklist-read-cli.ts +20 -10
- package/src/cli/release-cli.ts +27 -1
- package/src/cli/research-cli.ts +4 -5
- package/src/cli/sim-cli.ts +7 -1
- package/src/cli/tasks-cli.ts +12 -4
- package/src/codex/models.ts +30 -15
- package/src/compact/auto.ts +8 -1
- package/src/control-plane/ledger.ts +52 -2
- package/src/control-plane/server.ts +16 -2
- package/src/core-turn/run-core-turn.ts +313 -89
- package/src/core-turn/run-goal-loop.ts +106 -2
- package/src/core-turn/types.ts +2 -0
- package/src/dashboard/acp-boot.ts +72 -3
- package/src/dashboard/classic-sidetab-mission-job.ts +58 -0
- package/src/dashboard/classic-sidetab-mission.ts +185 -0
- package/src/dashboard/index.ts +155 -15
- package/src/dashboard/input/agent-wake.ts +23 -1
- package/src/dashboard/input/chat-main-plain-turn-runtime.ts +9 -0
- package/src/dashboard/mission-cli-consumer.ts +153 -0
- package/src/dashboard/seat-boot-preamble.ts +171 -1
- package/src/dashboard/side-basic-runtime.ts +54 -1
- package/src/dashboard/sidetab-runtime.ts +90 -1
- package/src/dashboard/skill-catalog-preamble.ts +101 -0
- package/src/dashboard/skill-detection-runtime.ts +55 -15
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +15 -0
- package/src/dashboard/turn-preamble.ts +65 -16
- package/src/dashboard/turn-prompt-runtime.ts +4 -2
- package/src/disk-steward/judge.ts +73 -0
- package/src/disk-steward/ledger.ts +122 -0
- package/src/disk-steward/steward.ts +373 -0
- package/src/domains/guardian-tick.ts +191 -2
- package/src/graph-runner/graph-cli.ts +44 -7
- package/src/graph-runner/graph-tick.ts +10 -2
- package/src/graph-runner/in-process-registry.ts +37 -0
- package/src/graph-runner/runner.ts +34 -4
- package/src/grok/agent-search.ts +14 -4
- package/src/grok/credential.ts +28 -11
- package/src/grounding/ref-index.ts +19 -0
- package/src/grounding/sources-cli.ts +9 -1
- package/src/harness/harness-cli-command.ts +267 -14
- package/src/harness/harness-incidents.ts +312 -7
- package/src/harness/harness-pod-dispatch.ts +81 -15
- package/src/harness/harness-space.ts +15 -1
- package/src/harness/harness-stop.ts +155 -11
- package/src/harness/progress-worker-entry.ts +59 -0
- package/src/harness/progress-worker-runtime.ts +11 -0
- package/src/index.ts +52 -7
- package/src/instance/dev-project.ts +25 -0
- package/src/instance/spawn-elanous.ts +6 -4
- package/src/intelligence-map/model-alias.ts +6 -5
- package/src/intelligence-map/model-catalog.ts +59 -2
- package/src/llm/model-defaults.ts +4 -4
- package/src/llm.ts +208 -19
- package/src/loops/orchestrator/coordinator-shadow.ts +226 -0
- package/src/loops/orchestrator/tick.ts +61 -0
- package/src/maturity/feature-maturity.ts +1 -1
- package/src/mcp/mcp-oauth.ts +3 -1
- package/src/model-tier/llm-tier-map.ts +9 -9
- package/src/model-tier/turn-tier.ts +1 -1
- package/src/msg/msg-mirror-github.ts +80 -0
- package/src/msg/msg-mirror-policy.ts +61 -0
- package/src/msg/msg-mirror-runner.ts +219 -0
- package/src/msg/msg-mirror-state.ts +33 -0
- package/src/msg/msg-store.ts +11 -0
- package/src/native-tool-catalog.ts +2 -2
- package/src/nexus/api/harness-traversal.ts +11 -0
- package/src/output-truncation.ts +41 -12
- package/src/pr-tool/census.ts +3 -2
- package/src/project/project-store.ts +48 -1
- package/src/pty-shell/tui-run-state.ts +188 -2
- package/src/release-loop/checklist-target-path-hint.ts +13 -0
- package/src/release-loop/checklist.ts +9 -1
- package/src/release-loop/gate-host-reservation.ts +144 -0
- package/src/release-loop/release-schedule.ts +90 -1
- package/src/repl/index.ts +43 -2
- package/src/research/axis-fanout.ts +121 -16
- package/src/research/bloom-exemplars.ts +137 -0
- package/src/research/gap-followup.ts +13 -1
- package/src/research/plugin-discovery.ts +36 -14
- package/src/research-bridge/invoke.ts +22 -5
- package/src/research-bridge/types.ts +2 -0
- package/src/self-dev/ask-launch-flow.ts +39 -25
- package/src/self-dev/decompose.ts +119 -5
- package/src/self-dev/dev-cli.ts +17 -17
- package/src/self-dev/graph-front-nodes.ts +23 -0
- package/src/self-dev/launch-preflight-text.ts +5 -6
- package/src/self-dev/launch-preflight.ts +9 -4
- package/src/self-dev/orchestrate.ts +27 -2
- package/src/self-dev/review-provider-fallback.ts +9 -2
- package/src/self-dev/run-supervisor.ts +226 -6
- package/src/self-dev/run-types.ts +7 -0
- package/src/self-implement/doctor-visit.ts +88 -0
- package/src/self-implement/gate-artifacts.ts +13 -0
- package/src/self-implement/gate-cli.ts +54 -24
- package/src/self-implement/gate-remote.ts +23 -5
- package/src/self-implement/goal-author-clarification.ts +31 -12
- package/src/self-implement/goal-author.ts +403 -8
- package/src/self-implement/graph-overlay-yaml.ts +7 -0
- package/src/self-implement/graph-stage-units.ts +172 -0
- package/src/self-implement/graph-templates.ts +162 -1
- package/src/self-implement/graph-variant.ts +37 -4
- package/src/self-implement/headless-elanous-driver.ts +31 -2
- package/src/self-implement/heal-intake.ts +112 -4
- package/src/self-implement/heal-triage.ts +20 -0
- package/src/self-implement/host-regate.ts +31 -8
- package/src/self-implement/knowhow-pack.ts +38 -0
- package/src/self-implement/merge-train.ts +5 -0
- package/src/self-implement/orchestrator.ts +359 -30
- package/src/self-implement/regate-detail.ts +38 -1
- package/src/self-implement/rework-child-provider.ts +25 -0
- package/src/self-implement/run-chart.ts +119 -0
- package/src/self-implement/run-ledger.ts +176 -17
- package/src/self-implement/run-stop.ts +208 -5
- package/src/self-implement/running-runs.ts +35 -12
- package/src/self-implement/self-implement-cli.ts +36 -4
- package/src/self-implement/stop-autoheal-operational.ts +43 -0
- package/src/self-implement/stop-autoheal.ts +28 -6
- package/src/session/chat.ts +69 -58
- package/src/session/request-size-guard.ts +123 -0
- package/src/session-runtime/index.ts +98 -4
- package/src/session-runtime/tier-flip.ts +3 -3
- package/src/sidetab-core/model.ts +150 -0
- package/src/sidetab-core/visible-agent-mission.ts +186 -0
- package/src/skills/name-match.ts +20 -0
- package/src/skills/tools/agent.ts +30 -2
- package/src/skills/tools/omni-search.ts +67 -5
- package/src/task-agent/parent-host.ts +14 -1
- package/src/task-agent/pod-distributor-entry.ts +3 -0
- package/src/task-agent/task-hand.ts +31 -1
- package/src/task-orchestrator/surfaces/pod-distributor.ts +48 -0
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +43 -0
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +38 -1
- package/src/task-orchestrator/surfaces/pod-ledger-events.ts +29 -4
- package/src/task-orchestrator/surfaces/pod-si-warm-entry.ts +33 -6
- package/src/task-orchestrator/surfaces/pod-source-bundle.ts +45 -9
- package/src/task-orchestrator/surfaces/pod-source-receive.ts +163 -13
- package/src/task-orchestrator/surfaces/pod-source-spec.ts +3 -3
- package/src/task-orchestrator/surfaces/pod-warm-worker-provider.ts +209 -39
- package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +80 -3
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +280 -60
- package/src/task-orchestrator/surfaces/self-implement.ts +18 -0
- package/src/tokens.ts +35 -2
- package/src/tool-runtime/agent-runtime.ts +1 -0
- package/src/tool-runtime/elanous-skills-list-runtime.ts +5 -1
- package/src/tool-runtime/skill-exec-runtime.ts +33 -13
- package/src/tool-runtime/types.ts +1 -0
- package/src/tui/entry.ts +24 -7
- package/src/tui/sidetab/attach.ts +47 -0
- package/src/tui/sidetab/list.ts +45 -0
- package/src/tui/sidetab/nl-tool.ts +2 -136
- package/src/tui/sidetab/peek.ts +52 -0
- package/src/tui-client/dashboard-session.ts +3 -0
- package/src/tui-client/local-chat-turn.ts +26 -13
- package/src/tui.ts +22 -0
- package/src/user-config.ts +11 -5
- package/src/ux-sim/fake-kubectl.ts +37 -3
- package/src/ux-sim/fault-needs-human-reason-repair.ts +107 -0
- package/src/ux-sim/fault-pod-child-result.ts +42 -0
- package/src/ux-sim/fault-pool-cap.ts +11 -6
- package/src/ux-sim/mock-scenario-catalog.json +41 -0
- package/src/ux-sim/mock-scenario-catalog.ts +156 -0
- package/src/ux-sim/release-sim-rig.ts +262 -0
- package/src/ux-sim/scenario.ts +116 -23
- package/src/ux-sim/seminar-rehearsal.ts +109 -0
- package/src/ux-sim/subagent-tools-device.ts +2 -2
- package/src/version/packed-revision.json +1 -1
- package/src/voice/voice-tts-singleton.ts +18 -3
- package/src/web-search/grok.ts +5 -9
- package/apps/pwa/out/_next/static/chunks/1060.014f1f01a17aec26.js +0 -115
- package/apps/pwa/out/_next/static/chunks/1415.79821ec7ae99acdb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1452.162bb2d9d699e82a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/147.f92f63bbc5e85fa5.js +0 -286
- package/apps/pwa/out/_next/static/chunks/1661.fb425a1d8a065b72.js +0 -149
- package/apps/pwa/out/_next/static/chunks/1671-1c22b40b50b208f2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1758.0204d3f48783c49d.js +0 -90
- package/apps/pwa/out/_next/static/chunks/1806.027bf78b6a94d340.js +0 -104
- package/apps/pwa/out/_next/static/chunks/2047.8f23e210995d4548.js +0 -2
- package/apps/pwa/out/_next/static/chunks/2061-7f2bbc53ae96c88b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2066.a77257c8c350770a.js +0 -23
- package/apps/pwa/out/_next/static/chunks/2096-a938496bc996dcbd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2200-9a453b8f99687d49.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2281-b8cede695c6c650e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2499.02fbfd18126c10c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2522.f2766b76d22c8bdf.js +0 -34
- package/apps/pwa/out/_next/static/chunks/2615.3c9738153de3ef3f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2727.e703d99ac414916d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2752.7904b8df0254d0ae.js +0 -27
- package/apps/pwa/out/_next/static/chunks/2762-5051a862dfa40e99.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2767-31784bfedf8e245d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2774-719820e3f5b2a3eb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2812-f016aa0dda149287.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2829-734fe8bf2eee7347.js +0 -1
- package/apps/pwa/out/_next/static/chunks/311-32dcac79970985e8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3175-f93f7495cd28253c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3184.8da993f5502d2140.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3231-d7c981db0ff8bdcc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3250.151309c5176d6b4b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3517.a5aa1797a9dbf2ee.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3567-45f676877ed9e8a5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3819.8a1de79b2e8c7298.js +0 -24
- package/apps/pwa/out/_next/static/chunks/39.9b37b0be4c128a83.js +0 -77
- package/apps/pwa/out/_next/static/chunks/4023.a19045970a9ce16a.js +0 -36
- package/apps/pwa/out/_next/static/chunks/413-24c8284bda08241d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4135.e45d6ebc29e20647.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4283.65502f5d0e6ff6c0.js +0 -287
- package/apps/pwa/out/_next/static/chunks/4305-4e28fa92a7b34a5b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4335-50efa61fd7613142.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4720.ecbd1693fa151068.js +0 -1
- package/apps/pwa/out/_next/static/chunks/483.725e4a61be922788.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4969-4b209d7307410c00.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5068-c7f53862f83c0917.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5315.bc96711c24dfefe2.js +0 -74
- package/apps/pwa/out/_next/static/chunks/5542.efbf14f16baed4dd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5622.94893f5aea7185d0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5644.8f401a25601d05fc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5658.35f70eb9871a54bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5721.7bdc0e904c76ab51.js +0 -8
- package/apps/pwa/out/_next/static/chunks/5790-eef0ca20884a6bdf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5802.ef0d8598ca68fe84.js +0 -62
- package/apps/pwa/out/_next/static/chunks/5890.fb974729788dcb71.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5924.5ef25cddd36d5dbf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5979-e59c099ebf8d1f35.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6020.714cfeada2315e2e.js +0 -94
- package/apps/pwa/out/_next/static/chunks/6027-dfe0349d8c50f698.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6178.bc4730c86cf74536.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6273.d808b0dcb3c2b155.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6292.6ef1df64fa6a4c8a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/647-af8dd920ba46533c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6541-170dc5c59e135c0c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6624.981ef7caeb680560.js +0 -32
- package/apps/pwa/out/_next/static/chunks/6742-a1ad4a94ba7bc37a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6836-493dc55c39187949.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6856-53ac421c06039a18.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6908.06008c67f6380ea9.js +0 -148
- package/apps/pwa/out/_next/static/chunks/6915.096931864206f522.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6925.5e181fb7dcf90193.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6cc1d1d6.9a98dd4622f1afc6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7203-800e11037adc41eb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7279.6170f591105936bb.js +0 -93
- package/apps/pwa/out/_next/static/chunks/7349-373165c7fbdcc3df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7514-a533ba0ca1cae4f4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7515.1ad6efe157fa4448.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7521.d919cc1fab80c1d9.js +0 -24
- package/apps/pwa/out/_next/static/chunks/7538.90e97bbdf9178cff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7825-39203196a12579b2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7869.5e1a9a12fd70d6c7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8086.1ab9103c41be884f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8179-e61fffbdb70f1152.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8265.bdeb991b34db6a06.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8357.1aaa713b052a78b1.js +0 -309
- package/apps/pwa/out/_next/static/chunks/8638-e42615f2166165b5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8639-92c11d069792429e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8691.54e22831d5e8ccf8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9069.657590a84afb8fac.js +0 -41
- package/apps/pwa/out/_next/static/chunks/9125-ae588d88a0293b93.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9311.f9f641a46a508611.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9371-a6fee8378ebfa3ee.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9421-3aa9ff4fa7f63d8a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9992.07ce73c10bd5e6a1.js +0 -79
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-76394dc618d322c8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-1b948637c37b3abd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-c63400f1bcf3e47c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-95d0957267819586.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-ba957ff875a026a0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-bb6fd0714879cb87.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-8f9038520d2acbef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-d4b48397e05fce2c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-72d5744245dc0c27.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-37625dc8300fa43a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-fbef9d160272dbc1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-58424168df84928e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-72c12ba430ac57c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-d53f3e9ed806d7e0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-4d2fbd45feee0829.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-76394dc618d322c8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-8783dc0ed7590a2c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-3706cb390606e930.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-9b0a86598e0111fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-4edb8df90f4537ce.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-47398b9ba1cc5e1b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-fb86f55cfe2e681d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-e22345fc8fd47b1b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-fca2e59a81744d9e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-6e96bda63100b31e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-4d17ad8e0db8cea4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-121f92766c33beaa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-43f14212c545f92d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-ccec2023e7a974ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-b04f33f548b89a94.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-c8792823c0917021.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-8c0e32782ceef4cb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-94bd3922eb230ce8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-bca565887bafa03c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-ac1dffafe649fc6c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-52374edb5e23e455.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-ad4ebca120d43ca7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-76394dc618d322c8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-718b89ce4b4ae4e4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-204eca33eeeaddec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-e58ba9bcdb9ac0f9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-98f6dd50bafd7734.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-5a4bdd72bff27057.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-6f6ab17d44f984ce.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-aabb7bd15fb72da9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-e4322319164a4436.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-e9efc0c8f32e2371.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-ccde99f07aaf71f7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-80464aced9614390.js +0 -1
- package/apps/pwa/out/_next/static/chunks/c696986b.5dc8bb1143af16d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/d5d3cefc-373c4eb722ed848b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/d8de0de2-dd0f4ac8e5ab1832.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-002b472a66935d31.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-49b88d47292c0476.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-fff2549cb10a133f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-32abbbba3f3b76b7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-9d9cfef60c60dd7a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-cc7f483fa294034a.js +0 -1
- package/apps/pwa/out/_next/static/css/d0a48dc099ed86cf.css +0 -1
- package/scripts/ratchets/next-tui-entry-budget.ts +0 -124
- /package/apps/pwa/out/_next/static/{HjeK7zONHfR7op1hKXp4k → X1ucLw10Cds_h614NoEnh}/_ssgManifest.js +0 -0
|
@@ -9,7 +9,7 @@ import { registerStandaloneLogSink } from '../../src/domains/standalone-log-sink
|
|
|
9
9
|
import { landingFreezeMessage, readLandingFreeze } from '../../src/release-loop/landing-freeze.js';
|
|
10
10
|
import { effectiveInstanceRoot, releaseLedgerRoot } from '../../src/instance/resolve.js';
|
|
11
11
|
import { getElanousConfigDirOverride } from '../../src/elanous-config-dir.js';
|
|
12
|
-
import { diffFailures, junitFailures, parseFailures, parseTimedOutFailures } from './gate-diff';
|
|
12
|
+
import { collapseGateFailureAttempts, diffFailures, GateFailureAttributionMismatch, junitFailures, parseFailureAttempts, parseFailures, parseTimedOutFailures } from './gate-diff';
|
|
13
13
|
import { loadEnvKnownFailures, splitKnownEnv } from './env-known-failures';
|
|
14
14
|
import { loadGateBatchSafe } from './gate-batch-safe';
|
|
15
15
|
import { observeGatePrewarmCutShadow, type GatePrewarmInput } from './gate-prewarm-plan.js';
|
|
@@ -17,9 +17,13 @@ import { gateHistory } from './gate-history.js';
|
|
|
17
17
|
import { baselineFromCutLogs } from './baseline-from-logs';
|
|
18
18
|
import { mergePartialFailures, readPartialPlanResult, testFileOfId, verifyDocsOnlyCarry } from './gate-partial';
|
|
19
19
|
import { planGateRadiusShadow, shadowActual, writeGateRadiusShadow, type GateRadiusShadowPlan } from './gate-radius-shadow';
|
|
20
|
-
import { planHardRehearsalRadius, partialGateImagesMatch, preflightGateImageDigest } from './gate-radius-live';
|
|
21
|
-
import { actualGatePodImageDigest, gateBaselineReceipt, gateEnvFingerprint, gateGraphPins, gateSourcePins,
|
|
20
|
+
import { planHardRehearsalRadius, partialGateImagesMatch, preflightGateExecutionIdentity, preflightGateImageDigest } from './gate-radius-live';
|
|
21
|
+
import { actualGatePodImageDigest, gateBaselineReceipt, gateEnvFingerprint, gateGraphPins, gateSourcePins, parseGatePodRuntimeIdentity,
|
|
22
22
|
POD_SWEEP_INTEGRATION_ONLY } from './gate-baseline-receipt';
|
|
23
|
+
import { gateImagePinPath, readGateImagePin, resolveGateImagePin, writeGateImagePin } from './gate-image-pin.js';
|
|
24
|
+
import { gateReceiptRegistryPath, planGateTrainReceiptCarry } from './gate-train-receipts.js';
|
|
25
|
+
import { gateEnvFingerprint as gateReceiptEnvFingerprint, gateLockfileHash } from '../../src/self-implement/gate-receipt-registry.js';
|
|
26
|
+
import { SPAWN_SYNC_MAX_BUFFER } from '../../src/util/spawn-sync-output.js';
|
|
23
27
|
export { POD_SWEEP_INTEGRATION_ONLY } from './gate-baseline-receipt';
|
|
24
28
|
import { logPlanWalltime, planDurations, readFileMemory } from './shard-plan';
|
|
25
29
|
import { defaultGateHostMirrorReaders, resolveGateHostMirror, type GateHostMirror, type GateHostMirrorReaders } from './gate-host-mirror';
|
|
@@ -30,7 +34,7 @@ import { readJunitFileSeconds } from './gate-method';
|
|
|
30
34
|
import { deriveCdpTestPatterns } from '../test-deterministic';
|
|
31
35
|
import { emitNodeResult, readGraphContext } from './node-verdict.js';
|
|
32
36
|
import { isGateExecutor, type GateExecutor } from './gate-executor.js';
|
|
33
|
-
import { runPodCommand, parsePodCpu, podCpuMillis, resolvePodCpu, type RunPodCommandOptions, type PodCommandResult, type PodCpu } from '../../src/task-orchestrator/surfaces/pod-command-job.js';
|
|
37
|
+
import { runPodCommand, parsePodCpu, podCpuMillis, resolvePodCpu, podCommandImageFor, type RunPodCommandOptions, type PodCommandResult, type PodCpu } from '../../src/task-orchestrator/surfaces/pod-command-job.js';
|
|
34
38
|
import { POD_BUN_CACHE_HOST_PATH, parseInstallSeconds, podBunCacheVolume } from '../../src/task-orchestrator/surfaces/pod-bun-cache.js';
|
|
35
39
|
import { PodPoolScheduler, parsePodPool, checkPodPool, defaultRemoteRun, snapshotWarmPoolMembers, type PodPoolMember } from '../../src/task-orchestrator/surfaces/pod-pool.js';
|
|
36
40
|
import { POD_INSTALL_SLOTS_DEFAULT, POD_INSTALL_SLOTS_HOST_PATH, installSlotScript } from '../../src/task-orchestrator/surfaces/pod-install-slots.js';
|
|
@@ -45,13 +49,15 @@ import { getUserConfig } from '../../src/user-config.js';
|
|
|
45
49
|
import { beginGateShardAttempt, finalizeTerminalGateShards, gateShardsPath, recordGateShardsVerdict, writeGateShards, type GateJobTerminal, type GateShard } from '../../src/release-loop/gate-shards.js';
|
|
46
50
|
import { appendGateShardLedger, gateShardLedgerPath, type GateShardLedgerRow } from '../../src/release-loop/gate-shard-ledger.js';
|
|
47
51
|
import { markGateShardUnmeasured, nextGateShardAttempt, readGateShardManifest, readGateShardResult, saveGateShardResult } from './gate-shard-receipts.js';
|
|
48
|
-
import { planGateShardDispatch, type GateShardReceipt } from './gate-shard-dispatch.js';
|
|
52
|
+
import { planGateShardDispatch, summarizeGateProgress, type GateProgressGroup, type GateShardReceipt } from './gate-shard-dispatch.js';
|
|
49
53
|
import { defaultKubectl } from '../../src/task-orchestrator/surfaces/self-implement-pod.js';
|
|
50
54
|
import { executeTuiNodeCheck, type ExecutedTuiCheck } from './tui-node-checks.js';
|
|
51
55
|
import { runWithReleaseGateTraffic } from './gate-traffic-mode.js';
|
|
52
56
|
import { emitPodLifecycle, gatePodLifecycleEvent, gatePodOutcome } from '../../src/task-orchestrator/surfaces/pod-lifecycle-stages.js';
|
|
53
57
|
import { gateHostReservationRoot, reserveReleaseGateHosts } from '../../src/release-loop/gate-host-reservation.js';
|
|
54
58
|
import { normRoot } from '../../src/instance/resolve.js';
|
|
59
|
+
import { cleanRetryVerdict, junitFailedCaseIds, junitTestcaseCount, junitTestcaseFiles } from './gate-shard-load-flaky.js';
|
|
60
|
+
import { decideAndRecordGateAutoheal, gateAutohealCause, type GateAutohealDecision } from './gate-autoheal.js';
|
|
55
61
|
|
|
56
62
|
export interface WarmShadowObservation {
|
|
57
63
|
files: string[];
|
|
@@ -76,6 +82,10 @@ export function warmCommandMatchesRequest(command: WarmWorkerCommand, request: O
|
|
|
76
82
|
}
|
|
77
83
|
|
|
78
84
|
interface CommandResult { rc: number; output: string; passedIds?: string[]; stalledEnv?: Array<{ file: string; reason: 'no-output' }>; timedOut?: boolean;
|
|
85
|
+
/** Same-Pod per-file retry passed with complete log/JUnit evidence; these original ids are excluded from introduced. */
|
|
86
|
+
loadFlaky?: string[];
|
|
87
|
+
/** Files already given their one same-Pod retry; prevent the later timeout-only path from retrying them again. */
|
|
88
|
+
loadFlakyAttemptedFiles?: string[];
|
|
79
89
|
/** Identity and complete selected inventory of this Pod sweep; only the producer may issue it. */
|
|
80
90
|
podSweep?: { id: string; shards: number; files: string[] };
|
|
81
91
|
/** Complete, source-bound one-file Job result for shadow comparison; absent means unmeasured. */
|
|
@@ -107,12 +117,28 @@ export interface GateTools {
|
|
|
107
117
|
podCommand?: (options: RunPodCommandOptions) => Promise<PodCommandResult>;
|
|
108
118
|
/** Kubernetes client (pool readiness · Pod lifecycle · Job log tail · failure evidence); absent = `defaultKubectl`. */
|
|
109
119
|
kubectl?: typeof defaultKubectl;
|
|
120
|
+
/** Mock-only imageID observation seam; production reads the completed Job's child imageID from Kubernetes. */
|
|
121
|
+
podImageDigest?: (input: { job: PodCommandResult; host: string }) => string | null;
|
|
110
122
|
/** SSH transport behind {@link GateRunner.command} on a remote host; absent = a real `ssh` child. */
|
|
111
123
|
ssh?: (host: string, script: string, limitMs?: number) => { rc: number; output: string; timedOut?: boolean };
|
|
112
124
|
/** SSH reachability probe of a multi-host roster; absent = `defaultRemoteRun`. */
|
|
113
125
|
remoteRun?: typeof defaultRemoteRun;
|
|
114
126
|
/** Pool admission a shard waits on (leases ⊕ a cluster reading); absent = the gate's own pool admission. */
|
|
115
127
|
admission?: GateAdmission;
|
|
128
|
+
/** The live progress heartbeat scheduler; tests inject a deterministic tick, production uses an unref'd interval. */
|
|
129
|
+
progressScheduler?: GateProgressScheduler;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export type GateProgressScheduler = (tick: () => void, intervalMs: number) => () => void;
|
|
133
|
+
const scheduleGateProgress: GateProgressScheduler = (tick, intervalMs) => {
|
|
134
|
+
const timer = setInterval(tick, intervalMs);
|
|
135
|
+
timer.unref();
|
|
136
|
+
return () => clearInterval(timer);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export interface GateSweepProgress {
|
|
140
|
+
executor: 'host' | 'pod';
|
|
141
|
+
groups: readonly GateProgressGroup[];
|
|
116
142
|
}
|
|
117
143
|
export interface GateRunner {
|
|
118
144
|
command(cmd: string, args: string[], cwd: string, limitMs?: number): Promise<CommandResult>;
|
|
@@ -122,7 +148,7 @@ export interface GateRunner {
|
|
|
122
148
|
/** One checkout/install for a small independent file group; each file retains its own rc and log. */
|
|
123
149
|
recheckPodBatch?(files: readonly string[], commit: string, pod: NonNullable<GateOptions['pod']>, timeoutMs: number): Promise<Map<string, CommandResult>>;
|
|
124
150
|
/** `only` (GATE-PARTIAL) restricts the sweep to these test files; absent = every test file. */
|
|
125
|
-
sweep(tree: string, logDir?: string, pod?: GateOptions['pod'], only?: readonly string[]): Promise<CommandResult>;
|
|
151
|
+
sweep(tree: string, logDir?: string, pod?: GateOptions['pod'], only?: readonly string[], onProgress?: (progress: GateSweepProgress) => void): Promise<CommandResult>;
|
|
126
152
|
add(tree: string, commit: string): Promise<void>;
|
|
127
153
|
remove(tree: string): Promise<void>;
|
|
128
154
|
snapshot(tree: string, commit: string): Promise<void>;
|
|
@@ -131,6 +157,7 @@ export interface GateRunner {
|
|
|
131
157
|
remoteMirror?: string;
|
|
132
158
|
/** GATE-FLOW-SIM — the clock this runner measures with (the injected fake clock in a scenario); absent = `Date.now`. */
|
|
133
159
|
now?(): number;
|
|
160
|
+
progressScheduler?: GateProgressScheduler;
|
|
134
161
|
}
|
|
135
162
|
|
|
136
163
|
/** One watchdog tick. Batch Kubernetes reads per host and trust only the current attempt's result marker. */
|
|
@@ -205,6 +232,8 @@ export interface GateOptions {
|
|
|
205
232
|
/** Only a direct hard rehearsal may pin graph bytes from its measured Git commit. */
|
|
206
233
|
rehearsalBaseline?: boolean;
|
|
207
234
|
forceFreeze?: boolean;
|
|
235
|
+
/** Force all release tests to run, bypassing every reusable partial plan. */
|
|
236
|
+
full?: boolean;
|
|
208
237
|
version: string;
|
|
209
238
|
baselineVersion?: string;
|
|
210
239
|
baselineCommit?: string;
|
|
@@ -221,6 +250,9 @@ export interface GateOptions {
|
|
|
221
250
|
recheckFileLimit?: number;
|
|
222
251
|
/** freshShards — GATE-SHARD-RESUME off: every shard starts a Pod even when this logDir already holds its result. */
|
|
223
252
|
pod?: { pool: string; shards?: number; shardTimeoutSeconds?: number; durationSource?: string; bunCache?: string; freshShards?: boolean;
|
|
253
|
+
/** One immutable container image identity for this gate graph run and all its resumes. */
|
|
254
|
+
pinnedImageDigest?: string;
|
|
255
|
+
pinnedRunId?: string;
|
|
224
256
|
/** Remeasure one named shard, or all incomplete shards with auto. Complete siblings keep their receipts. */
|
|
225
257
|
rerouteShard?: string | 'auto';
|
|
226
258
|
/** Pool context for the rerouted shard; the planner otherwise distributes missing shards over pool contexts. */
|
|
@@ -331,13 +363,16 @@ export interface GateResult {
|
|
|
331
363
|
baselineSource?: 'ledger' | 'instance' | 'cut-logs' | 'swept';
|
|
332
364
|
/** GATE-BASELINE-CACHE — isolated baseline files answered from the ledger (hits) or run now (misses). */
|
|
333
365
|
baselineCache?: { hits: number; misses: number };
|
|
366
|
+
/** A residual summary/name mismatch triggers an explicit baseline rerun before the gate stays fail-closed. */
|
|
367
|
+
attributionRecheck?: { summary: number; observed: number; identified: number; collapsed: number;
|
|
368
|
+
targetFiles: string[]; baselineResult: 'completed' | 'failed' | 'timeout' };
|
|
334
369
|
/** GATE-SPEED A4 — a swept baseline ran only the cut's failing files that exist at the previous release. */
|
|
335
370
|
baselineDeferred?: { cutFiles: number; swept: number };
|
|
336
371
|
durationMs: number;
|
|
337
372
|
/** GATE-WARM-BOOT-SCAN — observation only: the cut sweep's test selection time (pod sweep · absent otherwise). */
|
|
338
373
|
selectionMs?: number;
|
|
339
374
|
error?: string;
|
|
340
|
-
stalledShards?: Array<{ shard: number; files: string[]; reason: 'incomplete' | 'no-output' | 'job-failed' | 'exit-code' | 'unattributed' | 'timeout' | 'admission-timeout' | 'admission-error'; lastFile?: string; summaryFailures?: number; namedFailures?: number; detail?: string;
|
|
375
|
+
stalledShards?: Array<{ shard: number; files: string[]; reason: 'incomplete' | 'no-output' | 'job-failed' | 'exit-code' | 'unattributed' | 'timeout' | 'admission-timeout' | 'admission-error' | 'image-pin-mismatch'; lastFile?: string; summaryFailures?: number; namedFailures?: number; detail?: string;
|
|
341
376
|
/** Pool member that ran the final unmeasured attempt; enables a bounded cross-host tail remeasure. */
|
|
342
377
|
lastHost?: string;
|
|
343
378
|
/** Failure of the optional cross-host tail attempt; original stall fields remain authoritative. */
|
|
@@ -351,14 +386,17 @@ export interface GateResult {
|
|
|
351
386
|
partialFailures?: SweepFailures;
|
|
352
387
|
/** GATE-PARTIAL — this verdict re-ran only these files and carried the prior gate's results for the rest. */
|
|
353
388
|
partial?: { priorCommit: string; files: number };
|
|
354
|
-
|
|
355
|
-
|
|
389
|
+
trainReceiptReuse?: { carried: number; total: number; receipts: number; reasons: string[] };
|
|
390
|
+
/** A failure that passed an evidence-checked isolated re-run: warning (flaky under load), not an introduced regression. */
|
|
356
391
|
loadFlaky?: string[];
|
|
357
392
|
/** File-level unhandled errors that reproduced or could not be measured in isolation. They keep the gate red. */
|
|
358
393
|
unmeasuredErrors?: Array<{ file: string; reason: 'reproduced' | 'timeout' | 'unreadable'; ids: string[] }>;
|
|
359
394
|
/** Shard-only unhandled errors whose file-level replay completed cleanly. */
|
|
360
395
|
interferenceErrors?: string[];
|
|
361
396
|
rehearsalTui?: ExecutedTuiCheck;
|
|
397
|
+
/** Recovery recommendation is persisted with the failed graph node so the resume coordinator can act on it. */
|
|
398
|
+
autoheal?: GateAutohealDecision;
|
|
399
|
+
autoheals?: GateAutohealDecision[];
|
|
362
400
|
}
|
|
363
401
|
|
|
364
402
|
const sha = /^[0-9a-f]{7,40}$/i;
|
|
@@ -768,7 +806,8 @@ export function gateShardShell(o: { parts: readonly (readonly string[])[]; cdpPa
|
|
|
768
806
|
// run_part body changes the shell hash once for this feature; keeping the
|
|
769
807
|
// multi-file invocation unchanged avoids another cache-key change.
|
|
770
808
|
const warmFile = part.length === 1 ? `WARM_REFERENCE_FILE=${quote(part[0]!)} ` : '';
|
|
771
|
-
|
|
809
|
+
const manifest = `printf '%s\\n' ${part.map(quote).join(' ')} > "$O/part-${index}.files"; `;
|
|
810
|
+
return `${manifest}${warmFile}run_part ${index} ${[...runner, ...(o.testTimeoutMs !== undefined ? ['--timeout', String(o.testTimeoutMs)] : spawnParts.includes(index) ? ['--timeout=120000'] : []), ...ignores, ...part.map(asPath)].map(quote).join(' ')}`;
|
|
772
811
|
});
|
|
773
812
|
// Only file-isolation batches use this path. A bounded group shares one clone/install, but every test process gets
|
|
774
813
|
// its own HOME, state and tmp directory; part logs/JUnit remain separate and the existing artifact reader judges them.
|
|
@@ -795,6 +834,7 @@ export function gateShardShell(o: { parts: readonly (readonly string[])[]; cdpPa
|
|
|
795
834
|
...(o.beatSeconds ? gateBeatScript(o.beatSeconds, o.installSlots > 0) : []),
|
|
796
835
|
...(o.cachePrefix ? [o.cachePrefix] : []),
|
|
797
836
|
gateEnvBaselineLine(),
|
|
837
|
+
`bun -e ${quote("const {createHash}=require('node:crypto');const keys=['ELANOUS_TEST_FAKE_LLM','LANG','LC_ALL','TZ'];const e=Object.fromEntries(keys.sort().map(k=>[k,process.env[k]??'']));const envFingerprint=createHash('sha256').update(JSON.stringify(e)).digest('hex');console.log(JSON.stringify({bunVersion:Bun.version,platform:process.platform,architecture:process.arch,envFingerprint}));")} > "$O/runtime-identity.json" 2> "$O/runtime-identity.err" || rm -f "$O/runtime-identity.json"`,
|
|
798
838
|
...(o.rulesBaseSha ? [`export ELANOUS_GATE_BASE_SHA=${quote(o.rulesBaseSha)}`] : []),
|
|
799
839
|
// Seconds since this container's PID 1 started (the clone ran before this shell) — 0 when /proc cannot tell.
|
|
800
840
|
`pod_age=$(awk -v t="$(cut -d' ' -f22 /proc/1/stat 2>/dev/null)" -v hz="$(getconf CLK_TCK 2>/dev/null)" '{ if (t > 0 && hz > 0) print int($1 - t / hz); else print 0 }' /proc/uptime 2>/dev/null)`,
|
|
@@ -826,6 +866,24 @@ export function gateShardShell(o: { parts: readonly (readonly string[])[]; cdpPa
|
|
|
826
866
|
...(spawnParts.length ? [' local test_start; test_start=$(date +%s)'] : []),
|
|
827
867
|
` { if [ -n "\${WARM_REFERENCE_FILE:-}" ]; then printf "[warm-worker] file=%s\\n" "$WARM_REFERENCE_FILE"; ${o.sourceBinding ? `printf "[warm-worker] cut=%s attempt=%s\\n" ${quote(o.sourceBinding.cutSha)} ${quote(o.sourceBinding.attemptId)}; ` : ''}fi; "\${run_env[@]}" timeout -k 10 "$left" "$@" --reporter=junit --reporter-outfile="$O/part-$i.junit.xml"; } > "$O/part-$i.log" 2>&1`,
|
|
828
868
|
' local prc=$?',
|
|
869
|
+
' if [ -s "$O/part-$i.junit.xml" ]; then',
|
|
870
|
+
' if bun run scripts/release-loop/gate-shard-load-flaky.ts "$O/part-$i.junit.xml" "$O/part-$i.retry-files" "$O/part-$i.files"; then',
|
|
871
|
+
' local retry_index=0 retry_left retry_rc retry_file',
|
|
872
|
+
' while IFS= read -r retry_file; do',
|
|
873
|
+
' [ -n "$retry_file" ] || continue',
|
|
874
|
+
' retry_left=$(( gate_end - $(date +%s) ))',
|
|
875
|
+
' [ "$retry_left" -gt 0 ] || break',
|
|
876
|
+
' [ "$retry_left" -le 120 ] || retry_left=120',
|
|
877
|
+
' stage "part-$i-retry-$retry_index-start"',
|
|
878
|
+
' : > "$O/part-$i.retry-$retry_index.started"',
|
|
879
|
+
' timeout -k 10 "$retry_left" "${run_env[@]}" bun run scripts/test-deterministic.ts "$retry_file" --timeout 120000 --reporter=junit --reporter-outfile="$O/part-$i.retry-$retry_index.junit.xml" > "$O/part-$i.retry-$retry_index.log" 2>&1',
|
|
880
|
+
' retry_rc=$?',
|
|
881
|
+
' echo "$retry_rc" > "$O/part-$i.retry-$retry_index.rc"',
|
|
882
|
+
' stage "part-$i-retry-$retry_index-end"',
|
|
883
|
+
' retry_index=$(( retry_index + 1 ))',
|
|
884
|
+
' done < "$O/part-$i.retry-files"',
|
|
885
|
+
' fi',
|
|
886
|
+
' fi',
|
|
829
887
|
' if [ -n "${WARM_REFERENCE_FILE:-}" ] && [ -f "$O/part-$i.junit.xml" ]; then',
|
|
830
888
|
' local junit_sha; junit_sha=$(sha256sum "$O/part-$i.junit.xml" 2>/dev/null | awk \'{print $1}\')',
|
|
831
889
|
' if [ -z "$junit_sha" ]; then junit_sha=$(shasum -a 256 "$O/part-$i.junit.xml" 2>/dev/null | awk \'{print $1}\'); fi',
|
|
@@ -1136,26 +1194,40 @@ export interface ShardArtifacts {
|
|
|
1136
1194
|
finished: string[];
|
|
1137
1195
|
/** Raw logs of the parts that did not finish (diagnostics · last started file). */
|
|
1138
1196
|
unfinishedLog?: string;
|
|
1197
|
+
runtimeIdentity?: ReturnType<typeof parseGatePodRuntimeIdentity>;
|
|
1198
|
+
/** Original failure ids whose one same-Pod standalone retry completed cleanly. */
|
|
1199
|
+
loadFlaky?: string[];
|
|
1200
|
+
/** Files for which a same-Pod retry completed, even if it remained failing. */
|
|
1201
|
+
loadFlakyAttemptedFiles?: string[];
|
|
1139
1202
|
}
|
|
1140
1203
|
/** Reads a shard Pod's outbox: the part layout ({@link gateShardShell}) or the older single-run one (shard.log · shard.rc · junit.xml). */
|
|
1141
1204
|
export function readShardArtifacts(dir: string, parts: readonly (readonly string[])[]): ShardArtifacts {
|
|
1142
1205
|
const read = (name: string) => { const path = join(dir, name); return existsSync(path) ? readFileSync(path, 'utf8') : undefined; };
|
|
1206
|
+
const identityText = read('runtime-identity.json');
|
|
1207
|
+
const runtimeIdentity = identityText === undefined ? undefined : parseGatePodRuntimeIdentity(identityText) ?? undefined;
|
|
1143
1208
|
const rcOf = (text: string | undefined) => { const value = text?.trim() ?? ''; return /^\d+$/.test(value) && Number.isSafeInteger(Number(value)) ? Number(value) : undefined; };
|
|
1144
1209
|
const installRc = rcOf(read('install.rc'));
|
|
1145
1210
|
const install = read('install.log');
|
|
1146
1211
|
if (installRc === undefined && install === undefined) {
|
|
1147
1212
|
const junit = read('junit.xml');
|
|
1148
1213
|
const rc = rcOf(read('shard.rc'));
|
|
1149
|
-
return { output: read('shard.log'), ...(junit !== undefined ? { junit } : {}), ...(rc !== undefined ? { rc } : {}),
|
|
1214
|
+
return { output: read('shard.log'), ...(junit !== undefined ? { junit } : {}), ...(rc !== undefined ? { rc } : {}),
|
|
1215
|
+
...(runtimeIdentity ? { runtimeIdentity } : {}), timedOut: false, finished: [] };
|
|
1150
1216
|
}
|
|
1151
1217
|
const timedOut = read('shard.timeout') !== undefined;
|
|
1152
|
-
if (installRc !== 0) return { output: install, ...(installRc !== undefined ? { rc: installRc } : {}),
|
|
1153
|
-
|
|
1218
|
+
if (installRc !== 0) return { output: install, ...(installRc !== undefined ? { rc: installRc } : {}),
|
|
1219
|
+
...(runtimeIdentity ? { runtimeIdentity } : {}), timedOut, finished: [] };
|
|
1220
|
+
const good: Array<{ log: string; junit?: string; rc: 0 | 1; files: readonly string[]; loadFlaky?: string[]; loadFlakyAttemptedFiles?: string[] }> = [];
|
|
1154
1221
|
const bad: string[] = [];
|
|
1155
1222
|
const exitAnomaly: string[] = [];
|
|
1223
|
+
const attemptedFiles = new Set<string>();
|
|
1156
1224
|
let otherBad = false;
|
|
1157
1225
|
let badRc: number | undefined;
|
|
1158
1226
|
parts.forEach((files, index) => {
|
|
1227
|
+
const retryManifest = (read(`part-${index}.retry-files`) ?? '').split(/\r?\n/).filter(Boolean);
|
|
1228
|
+
retryManifest.forEach((file, retryIndex) => {
|
|
1229
|
+
if (files.includes(file) && read(`part-${index}.retry-${retryIndex}.started`) !== undefined) attemptedFiles.add(file);
|
|
1230
|
+
});
|
|
1159
1231
|
const log = read(`part-${index}.log`);
|
|
1160
1232
|
const rc = rcOf(read(`part-${index}.rc`));
|
|
1161
1233
|
const clean = log?.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '');
|
|
@@ -1166,9 +1238,32 @@ export function readShardArtifacts(dir: string, parts: readonly (readonly string
|
|
|
1166
1238
|
otherBad = true;
|
|
1167
1239
|
return;
|
|
1168
1240
|
}
|
|
1169
|
-
|
|
1241
|
+
const summary = clean === undefined ? undefined : lastMatch(clean, /Ran (\d+) tests? across (\d+) files?/);
|
|
1242
|
+
const summaryTests = Number(summary?.[1] ?? NaN);
|
|
1243
|
+
const summaryPass = clean === undefined ? NaN : summaryCount(clean, 'pass');
|
|
1244
|
+
const summaryFail = clean === undefined ? NaN : summaryCount(clean, 'fail');
|
|
1245
|
+
const summaryErrors = clean === undefined ? NaN : summaryCount(clean, 'error');
|
|
1246
|
+
const partJunit = read(`part-${index}.junit.xml`) ?? '';
|
|
1247
|
+
const testcaseFiles = junitTestcaseFiles(partJunit);
|
|
1248
|
+
const expectedFiles = [...files].sort();
|
|
1249
|
+
const timedOutComplete = rc === 124 && summary !== undefined && summary !== null && Number(summary[2]) === files.length
|
|
1250
|
+
&& summaryPass + summaryFail + (Number.isNaN(summaryErrors) ? 0 : summaryErrors) === summaryTests
|
|
1251
|
+
&& junitTestcaseCount(partJunit) === summaryTests
|
|
1252
|
+
&& testcaseFiles !== null && testcaseFiles.length === expectedFiles.length && expectedFiles.every((file, fileIndex) => testcaseFiles[fileIndex] === file);
|
|
1253
|
+
if (clean !== undefined && ((rc === 0 || rc === 1) || timedOutComplete) && summary) {
|
|
1170
1254
|
const junit = read(`part-${index}.junit.xml`);
|
|
1171
|
-
|
|
1255
|
+
const retryFiles = retryManifest;
|
|
1256
|
+
const loadFlaky = retryFiles.flatMap((file, retryIndex) => {
|
|
1257
|
+
if (!files.includes(file)) return [];
|
|
1258
|
+
const retryRc = rcOf(read(`part-${index}.retry-${retryIndex}.rc`));
|
|
1259
|
+
const retryLog = read(`part-${index}.retry-${retryIndex}.log`);
|
|
1260
|
+
const retryJunit = read(`part-${index}.retry-${retryIndex}.junit.xml`);
|
|
1261
|
+
const originalFailures = (junitFailedCaseIds(junit ?? '') ?? []).filter((id) => fileOf(id) === file);
|
|
1262
|
+
const verdict = cleanRetryVerdict(retryRc, retryLog, retryJunit, file, originalFailures);
|
|
1263
|
+
if (!verdict.clean) return [];
|
|
1264
|
+
return originalFailures;
|
|
1265
|
+
});
|
|
1266
|
+
good.push({ log: clean, rc: rc === 0 ? 0 : 1, files, ...(junit !== undefined ? { junit } : {}), ...(loadFlaky.length ? { loadFlaky } : {}) });
|
|
1172
1267
|
return;
|
|
1173
1268
|
}
|
|
1174
1269
|
if (log !== undefined) bad.push(`# part ${index} (${rc === undefined ? 'unfinished' : `rc=${rc}`})\n${log}`);
|
|
@@ -1194,8 +1289,12 @@ export function readShardArtifacts(dir: string, parts: readonly (readonly string
|
|
|
1194
1289
|
...(rc !== undefined ? { rc } : {}),
|
|
1195
1290
|
timedOut: !complete && timedOut && badRc === undefined,
|
|
1196
1291
|
finished: good.flatMap((part) => [...part.files]),
|
|
1292
|
+
...([ ...new Set(good.flatMap((part) => part.loadFlaky ?? [])) ].length
|
|
1293
|
+
? { loadFlaky: [...new Set(good.flatMap((part) => part.loadFlaky ?? []))].sort() } : {}),
|
|
1294
|
+
...(attemptedFiles.size ? { loadFlakyAttemptedFiles: [...attemptedFiles].sort() } : {}),
|
|
1197
1295
|
...(bad.length ? { unfinishedLog: bad.join('\n') } : {}),
|
|
1198
1296
|
...(exitAnomaly.length && !otherBad ? { exitCodeOnly: exitAnomaly } : {}),
|
|
1297
|
+
...(runtimeIdentity ? { runtimeIdentity } : {}),
|
|
1199
1298
|
};
|
|
1200
1299
|
}
|
|
1201
1300
|
|
|
@@ -1290,7 +1389,6 @@ function failuresOf(run: CommandResult, label: string): SweepFailures {
|
|
|
1290
1389
|
const errorCount = subruns?.error ?? (Number.isNaN(reportedErrors) ? 0 : reportedErrors);
|
|
1291
1390
|
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
1391
|
const count = subruns?.fail ?? summaryCount(output, 'fail');
|
|
1293
|
-
const failures = parseFailures(output);
|
|
1294
1392
|
// Bun repeats failed test names under "N tests failed:". A validated Pod sweep joins
|
|
1295
1393
|
// separately checked shards, so a summary in one shard can precede real failures
|
|
1296
1394
|
// from later shards. Remove each repeated block, not everything after the last one.
|
|
@@ -1301,6 +1399,11 @@ function failuresOf(run: CommandResult, label: string): SweepFailures {
|
|
|
1301
1399
|
? output.replace(/^\d+ tests? failed:\r?\n(?:\(fail\)[^\r\n]*(?:\r?\n|$))*(?:[ \t]*\r?\n)*/gm, '')
|
|
1302
1400
|
: failureSummary ? output.slice(0, failureSummary.index) : output;
|
|
1303
1401
|
const occurrences = [...executionOutput.matchAll(/^[ \t]*\(fail\)[ \t]+.+?(?:[ \t]+\[[\d.]+(?:ms|s)\])?[ \t]*\r?$/gm)].length;
|
|
1402
|
+
const collapsed = collapseGateFailureAttempts(parseFailureAttempts(executionOutput));
|
|
1403
|
+
const failures = collapsed.failures;
|
|
1404
|
+
if (collapsed.collapsed > 0) debug.log('release-loop.gate', 'failure-attribution-deduplicated', {
|
|
1405
|
+
label, summary: count, observed: occurrences, identified: collapsed.failures.length, collapsed: collapsed.collapsed,
|
|
1406
|
+
});
|
|
1304
1407
|
// A test that leaves process.exitCode set leaks a non-zero exit into a run whose own summary is complete and clean
|
|
1305
1408
|
// (0.2.7 gate: agent-cli at v0.2.6 · 12 pass · 0 fail · rc 2). The summary is the verdict; read that exit as clean.
|
|
1306
1409
|
const leakedExit = !!ran && run.rc !== 0 && run.rc !== 1 && count === 0 && errorCount === 0 && occurrences === 0
|
|
@@ -1319,7 +1422,11 @@ function failuresOf(run: CommandResult, label: string): SweepFailures {
|
|
|
1319
1422
|
}
|
|
1320
1423
|
}
|
|
1321
1424
|
if (errors.length !== errorCount) throw incomplete(`error attribution identified=${errors.length}`);
|
|
1322
|
-
|
|
1425
|
+
const retryDuplicatesExplainCount = occurrences > count && collapsed.observed === occurrences && failures.length === count;
|
|
1426
|
+
if (Number.isFinite(count) && (failures.length !== count || (occurrences !== count && !retryDuplicatesExplainCount))) {
|
|
1427
|
+
throw new GateFailureAttributionMismatch(label, count, occurrences, failures.length, collapsed.collapsed, failures);
|
|
1428
|
+
}
|
|
1429
|
+
if (!Number.isFinite(count) || (count > 0 && failures.length === 0)
|
|
1323
1430
|
|| (rc === 1 && count === 0 && errorCount === 0) || (rc === 0 && count + errorCount > 0)) {
|
|
1324
1431
|
throw incomplete(`failure attribution incomplete: summary=${count}, identified=${failures.length}`);
|
|
1325
1432
|
}
|
|
@@ -1374,6 +1481,7 @@ class StalledPodShards extends Error {
|
|
|
1374
1481
|
const exitCode = stalledShards.filter((item) => item.reason === 'exit-code');
|
|
1375
1482
|
super(`pod sweep incomplete: stalled shards ${stalledShards.map(({ shard }) => shard).join(', ')}`
|
|
1376
1483
|
+ (unmeasured.length ? ` (unmeasured: no test output within ${unmeasured[0]!.stall!.seconds}s of checkout — shards ${[...new Set(unmeasured.map(({ shard }) => shard))].join(', ')})` : '')
|
|
1484
|
+
+ (stalledShards.some((item) => item.reason === 'image-pin-mismatch') ? ` (image pin mismatch: ${stalledShards.filter((item) => item.reason === 'image-pin-mismatch').map((item) => item.detail ?? `shard ${item.shard}`).join('; ')})` : '')
|
|
1377
1485
|
+ (exitCode.length ? ` (exit-code anomaly: 0 fail but exit ≠ 0/1 — ${[...new Set(exitCode.flatMap(({ files }) => files))].join(', ')})` : '')
|
|
1378
1486
|
+ (abortedShards ? ` (중단 조각 ${abortedShards}: mid-run-stall)` : ''));
|
|
1379
1487
|
}
|
|
@@ -1408,6 +1516,7 @@ class SettledRecheckFailures extends Error {
|
|
|
1408
1516
|
|
|
1409
1517
|
/** Isolated re-runs (host stall re-run · cut and baseline re-checks) are time-limited; sweeps and installs keep their full time. */
|
|
1410
1518
|
export const GATE_ISOLATED_TIMEOUT_MS = 300_000;
|
|
1519
|
+
export const GATE_ATTRIBUTION_RECHECK_FILE_LIMIT = 64;
|
|
1411
1520
|
/** Default final recheck window size; overflow continues in later windows. */
|
|
1412
1521
|
export const GATE_HOST_RECHECK_FILE_LIMIT = 32;
|
|
1413
1522
|
/** Two isolated test processes in each of four Pods = eight at most, with one checkout/install per eight files. */
|
|
@@ -1602,7 +1711,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1602
1711
|
if (!/^[0-9a-f]{40}$/.test(base)) throw new Error(`rules gate base invalid for ${commit.slice(0, 12)}: ${base}`);
|
|
1603
1712
|
return base;
|
|
1604
1713
|
};
|
|
1605
|
-
const podSweep = async (tree: string, logDir: string | undefined, pod: NonNullable<GateOptions['pod']>, only?: readonly string[]): Promise<CommandResult> => {
|
|
1714
|
+
const podSweep = async (tree: string, logDir: string | undefined, pod: NonNullable<GateOptions['pod']>, only?: readonly string[], onProgress?: (progress: GateSweepProgress) => void): Promise<CommandResult> => {
|
|
1606
1715
|
const sweepId = randomUUID();
|
|
1607
1716
|
const shardCount = pod.shards ?? 24;
|
|
1608
1717
|
const isolationConcurrency = pod.isolationConcurrency ?? 4;
|
|
@@ -1832,7 +1941,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1832
1941
|
return { ...(podName ? { podName } : {}), ...(termination ? { termination } : {}), ...(logs?.status === 0 ? { logTail: logs.stdout } : {}),
|
|
1833
1942
|
...(errors.length ? { error: `수거 실패 · ${errors.join(' · ')}` } : {}) };
|
|
1834
1943
|
});
|
|
1835
|
-
type ShardRun = { output: string; rc: number; pass: number; fail: number; errors: number; ran: number; files: number; passedIds: string[] };
|
|
1944
|
+
type ShardRun = { output: string; rc: number; pass: number; fail: number; errors: number; ran: number; files: number; passedIds: string[]; loadFlaky?: string[]; loadFlakyAttemptedFiles?: string[] };
|
|
1836
1945
|
type ShardReason = NonNullable<GateResult['stalledShards']>[number]['reason'];
|
|
1837
1946
|
// 깊이 끝 OOM 조각은 «조각 전체»를 파일 하나씩 격리한다 — 상한 40 이면 나머지가 못 잰 채 남았다(09-30 ① 7번 조각 110 파일).
|
|
1838
1947
|
const isolationRemaining = shards.map((item) => item.files.length);
|
|
@@ -1862,6 +1971,13 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1862
1971
|
// GATE-LIVE-OBS — root rows stay stable; each recursive split/retry and tail gets its own row.
|
|
1863
1972
|
// Otherwise a finished root Job can hide a still-running child (or tail) behind 80/80 done.
|
|
1864
1973
|
const board: GateShard[] = shards.map((item, index) => ({ id: `pod-${index}`, state: 'pending', plannedMin: Math.round(item.plannedSeconds / 6) / 10 }));
|
|
1974
|
+
const reportProgress = () => onProgress?.({ executor: 'pod', groups: board.map((row) => ({
|
|
1975
|
+
id: row.id, state: row.state === 'retry' ? 'running' : row.state,
|
|
1976
|
+
...(row.host ? { host: row.host } : {}),
|
|
1977
|
+
...(row.startedAt ? { startedAtMs: Date.parse(row.startedAt) } : {}),
|
|
1978
|
+
...(row.endedAt ? { endedAtMs: Date.parse(row.endedAt) } : {}),
|
|
1979
|
+
})) });
|
|
1980
|
+
reportProgress();
|
|
1865
1981
|
const filesById = new Map(shards.map((item, index) => [`pod-${index}`, [...item.files]]));
|
|
1866
1982
|
const detailsById = new Map<string, Partial<GateShardLedgerRow>>();
|
|
1867
1983
|
const observeShard = (row: GateShard) => {
|
|
@@ -1898,6 +2014,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1898
2014
|
for (const key of clear) delete next[key];
|
|
1899
2015
|
if (JSON.stringify(next) === JSON.stringify(row)) return;
|
|
1900
2016
|
board[index] = next;
|
|
2017
|
+
reportProgress();
|
|
1901
2018
|
observeShard(next);
|
|
1902
2019
|
flushBoard();
|
|
1903
2020
|
};
|
|
@@ -2001,15 +2118,19 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2001
2118
|
let podStages: Record<string, number> | undefined;
|
|
2002
2119
|
let podLifecycle: Record<string, string> | undefined;
|
|
2003
2120
|
let imageDigest: string | null = null;
|
|
2121
|
+
let runtimeIdentity: ReturnType<typeof parseGatePodRuntimeIdentity> | undefined;
|
|
2004
2122
|
let podJobName: string | undefined;
|
|
2005
2123
|
let batchArtifactsComplete = true;
|
|
2006
2124
|
let junit: string | undefined;
|
|
2007
2125
|
let rc: number | undefined;
|
|
2126
|
+
let shardLoadFlaky: string[] = [];
|
|
2127
|
+
let shardLoadFlakyAttemptedFiles: string[] = [];
|
|
2008
2128
|
let jobExitCode: number | undefined;
|
|
2009
2129
|
let batchArtifactDir: string | undefined;
|
|
2010
2130
|
let lastFile: string | undefined;
|
|
2011
2131
|
let jobFailed = false;
|
|
2012
2132
|
let jobError = '';
|
|
2133
|
+
let imagePinMismatch = false;
|
|
2013
2134
|
let failureCapture: ReturnType<NonNullable<NonNullable<GateOptions['pod']>['failureEvidence']>> | undefined;
|
|
2014
2135
|
let jobNameForCapture: string | undefined;
|
|
2015
2136
|
let jobContextForCapture: string | undefined;
|
|
@@ -2122,7 +2243,9 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2122
2243
|
// poolOverride is the injected scheduler used by isolated tests; it has no authenticated kubectl
|
|
2123
2244
|
// context. Those sweeps intentionally cannot mint a valid baseline receipt (missing observed digest).
|
|
2124
2245
|
// Production pool sweeps always use this Kubernetes lookup; lookup failure is receipt-invalid only.
|
|
2125
|
-
if (
|
|
2246
|
+
if (tools.podImageDigest) {
|
|
2247
|
+
imageDigest = tools.podImageDigest({ job, host: placedOn ?? 'unobserved' });
|
|
2248
|
+
} else if (!poolOverride && placedOn) {
|
|
2126
2249
|
try {
|
|
2127
2250
|
const got = kubectl(['--context', placedOn, '-n', 'elanous-test', 'get', 'pods', '-l', `job-name=${job.job}`, '-o', 'json']);
|
|
2128
2251
|
if (got.status === 0) {
|
|
@@ -2131,10 +2254,43 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2131
2254
|
}
|
|
2132
2255
|
} catch { /* timings never change the verdict */ }
|
|
2133
2256
|
}
|
|
2257
|
+
if (pod.pinnedImageDigest && imageDigest !== pod.pinnedImageDigest) {
|
|
2258
|
+
imagePinMismatch = true;
|
|
2259
|
+
debug.log('release-loop.gate', 'image-pin-mismatch', { runId: pod.pinnedRunId ?? null,
|
|
2260
|
+
shard, branch, host: placedOn ?? null, pinnedDigest: pod.pinnedImageDigest, actualDigest: imageDigest,
|
|
2261
|
+
decision: 'reject-job' }, { level: 'error' });
|
|
2262
|
+
throw new Error(`gate image pin mismatch: expected ${pod.pinnedImageDigest}, got ${imageDigest ?? 'unmeasured'}`);
|
|
2263
|
+
}
|
|
2134
2264
|
jobExitCode = job.exitCode;
|
|
2135
2265
|
if (job.exitCode !== 0) captureFailure();
|
|
2136
2266
|
batchArtifactDir = job.artifactsDir;
|
|
2137
2267
|
const read = readShardArtifacts(job.artifactsDir, parts);
|
|
2268
|
+
runtimeIdentity = read.runtimeIdentity;
|
|
2269
|
+
if (logDir) {
|
|
2270
|
+
mkdirSync(logDir, { recursive: true });
|
|
2271
|
+
let retryArtifactIndex = 0;
|
|
2272
|
+
const retryEvidence = parts.flatMap((files, partIndex) => {
|
|
2273
|
+
const path = join(job.artifactsDir, `part-${partIndex}.retry-files`);
|
|
2274
|
+
if (!existsSync(path)) return [];
|
|
2275
|
+
const retryFiles = readFileSync(path, 'utf8').split(/\r?\n/).filter((file) => file && files.includes(file));
|
|
2276
|
+
return retryFiles.map((file, retryIndex) => {
|
|
2277
|
+
const prefix = `part-${partIndex}.retry-${retryIndex}`;
|
|
2278
|
+
const persisted: Record<string, unknown> = { file, started: existsSync(join(job.artifactsDir, `${prefix}.started`)), rc: existsSync(join(job.artifactsDir, `${prefix}.rc`))
|
|
2279
|
+
? readFileSync(join(job.artifactsDir, `${prefix}.rc`), 'utf8').trim() : null };
|
|
2280
|
+
for (const suffix of ['log', 'junit.xml'] as const) {
|
|
2281
|
+
const source = join(job.artifactsDir, `${prefix}.${suffix}`);
|
|
2282
|
+
if (!existsSync(source)) continue;
|
|
2283
|
+
const targetName = `pod-${shard}${branch}.retry-${retryArtifactIndex}.${suffix}`;
|
|
2284
|
+
copyFileSync(source, join(logDir, targetName)); chmodSync(join(logDir, targetName), 0o600);
|
|
2285
|
+
persisted[suffix === 'log' ? 'log' : 'junit'] = targetName;
|
|
2286
|
+
}
|
|
2287
|
+
retryArtifactIndex++;
|
|
2288
|
+
return persisted;
|
|
2289
|
+
});
|
|
2290
|
+
});
|
|
2291
|
+
if (retryEvidence.length) writeFileSync(join(logDir, `pod-${shard}${branch}.retries.json`), JSON.stringify(retryEvidence, null, 2) + '\n', { mode: 0o600 });
|
|
2292
|
+
else rmSync(join(logDir, `pod-${shard}${branch}.retries.json`), { force: true });
|
|
2293
|
+
}
|
|
2138
2294
|
batchArtifactsComplete = !batchIsolation || parts.every((_, index) =>
|
|
2139
2295
|
['rc', 'log', 'junit.xml'].every((suffix) => existsSync(join(job.artifactsDir, `part-${index}.${suffix}`))));
|
|
2140
2296
|
const stagesPath = join(job.artifactsDir, 'stages.tsv');
|
|
@@ -2149,6 +2305,8 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2149
2305
|
}
|
|
2150
2306
|
}
|
|
2151
2307
|
stamp('artifactsRead');
|
|
2308
|
+
shardLoadFlaky = read.loadFlaky ?? [];
|
|
2309
|
+
shardLoadFlakyAttemptedFiles = read.loadFlakyAttemptedFiles ?? [];
|
|
2152
2310
|
({ output, junit, rc, timedOut, finished, unfinishedLog, exitCodeOnly } = read);
|
|
2153
2311
|
// A stall counts only when the Job also left no verdict (a failed stop can let a late shard finish after all).
|
|
2154
2312
|
const stalledAt = watch?.stalled();
|
|
@@ -2201,6 +2359,8 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2201
2359
|
if (unfinishedLog !== undefined) writeFileSync(unfinishedDestination, unfinishedLog, { mode: 0o600 });
|
|
2202
2360
|
else rmSync(unfinishedDestination, { force: true });
|
|
2203
2361
|
writeFileSync(join(logDir, `pod-${shard}${branch}.json`), JSON.stringify({ completed: true, jobName: jobNameForCapture, durationMs, rc: rc ?? null,
|
|
2362
|
+
...(shardLoadFlakyAttemptedFiles.length ? { samePodRetryFiles: shardLoadFlakyAttemptedFiles } : {}),
|
|
2363
|
+
...(shardLoadFlaky.length ? { loadFlaky: shardLoadFlaky } : {}),
|
|
2204
2364
|
...(stall ? { reason: 'stall', finishedFiles: 0, stallSeconds: stall.seconds, sinceCheckoutMs: stall.sinceCheckoutMs } : timedOut ? { reason: 'timeout', finishedFiles: finished.length } : {}), files: paths, plannedSeconds: estimated(paths), deadlineSeconds: shardDeadline, shardCount: shards.length, sweepId, commit, runKey, attemptId, ...(parentAttempt ? { parentAttempt } : {}), jobExitCode: jobExitCode ?? null, jobFailed, ...(placedOn ? { host: placedOn } : {}),
|
|
2205
2365
|
...(Object.keys(phaseTimes).length ? { phaseTimes } : {}), ...(podStages ? { podStages } : {}),
|
|
2206
2366
|
...(podLifecycle && Object.keys(podLifecycle).length ? { podLifecycle } : {}),
|
|
@@ -2214,7 +2374,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2214
2374
|
...(failureCapture.logTail !== undefined ? { logTail: redactedGateLogTail(failureCapture.logTail) } : {}),
|
|
2215
2375
|
...(failureCapture.error ? { error: redactSecretText(failureCapture.error) } : {}),
|
|
2216
2376
|
} } : {}),
|
|
2217
|
-
...(imageDigest ? { imageDigest } : {}),
|
|
2377
|
+
...(imageDigest ? { imageDigest } : {}), ...(runtimeIdentity ? { runtimeIdentity } : {}),
|
|
2218
2378
|
...(fileSeconds !== undefined ? { fileSeconds, testSeconds } : {}), ...(testWallSeconds !== undefined ? { testWallSeconds } : {}),
|
|
2219
2379
|
...(podOverheadSeconds !== undefined ? { podOverheadSeconds } : {}),
|
|
2220
2380
|
...(unfinishedLogBytes !== undefined ? { unfinishedLogBytes } : {}),
|
|
@@ -2248,6 +2408,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2248
2408
|
let namedFailures: number | undefined;
|
|
2249
2409
|
let unattributedDetail: string | undefined;
|
|
2250
2410
|
if (descent === 'split') reason = 'incomplete';
|
|
2411
|
+
else if (imagePinMismatch) reason = 'image-pin-mismatch';
|
|
2251
2412
|
// GATE-POD-STALL-FAILFAST: stopped with no test output — the existing «no output» reason (one fresh retry).
|
|
2252
2413
|
else if (stall) reason = 'no-output';
|
|
2253
2414
|
else if (timedOut) reason = 'timeout';
|
|
@@ -2366,6 +2527,8 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2366
2527
|
}
|
|
2367
2528
|
}
|
|
2368
2529
|
if (reason) {
|
|
2530
|
+
if (reason === 'image-pin-mismatch') return { runs: [], stalled: [{ shard, files: paths, reason,
|
|
2531
|
+
detail: jobError.slice(0, 300), ...(placedOn ? { lastHost: placedOn } : {}) }] };
|
|
2369
2532
|
// GATE-SKIP-KNOWN-RETRY — a shard whose summary is complete but whose failures could not be read back (unattributed)
|
|
2370
2533
|
// and whose every named failure is one the previous release already had is not re-run: re-running only re-measures
|
|
2371
2534
|
// known failures (0.2.18 cut: 109 retry Pods · 1,840 shard-minutes, 4x the first pass). Those failures go to the
|
|
@@ -2448,7 +2611,9 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2448
2611
|
if (reused) debug.log('release-loop.gate', 'pod-shard-resumed', { shard, branch, files: paths.length, rc, durationMs: reused.durationMs ?? null });
|
|
2449
2612
|
return { runs: [{ output: clean!, rc: rc!, pass: passes, fail: fails,
|
|
2450
2613
|
errors: Number.isNaN(errors) ? 0 : errors,
|
|
2451
|
-
ran: Number(ran![1]), files: Number(ran![2]), passedIds: junit ? junitPassedIds(junit) : []
|
|
2614
|
+
ran: Number(ran![1]), files: Number(ran![2]), passedIds: junit ? junitPassedIds(junit) : [],
|
|
2615
|
+
...(shardLoadFlaky.length ? { loadFlaky: shardLoadFlaky } : {}),
|
|
2616
|
+
...(shardLoadFlakyAttemptedFiles.length ? { loadFlakyAttemptedFiles: shardLoadFlakyAttemptedFiles } : {}) }], stalled: [] };
|
|
2452
2617
|
};
|
|
2453
2618
|
const runShard = async (...args: Parameters<typeof runShardImpl>): ReturnType<typeof runShardImpl> => {
|
|
2454
2619
|
const [paths, shard, depth = 0, branch = '', , , parentAttempt, hostRetries = 0] = args;
|
|
@@ -2516,7 +2681,10 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2516
2681
|
// has no base/environment/image binding and cannot authorize this cross-host measurement.
|
|
2517
2682
|
// The sixth runShardImpl argument is `fresh`: a missing receipt must launch a new Job.
|
|
2518
2683
|
const freshForReceipt = !!receiptRoot || pod.freshShards === true;
|
|
2519
|
-
const done = await withRootSlot(() =>
|
|
2684
|
+
const done = await withRootSlot(() => {
|
|
2685
|
+
markShard(shard, { state: 'running', startedAt: new Date().toISOString() }, ['waitReason']);
|
|
2686
|
+
return runShard(item.files, shard, 0, '', false, freshForReceipt);
|
|
2687
|
+
});
|
|
2520
2688
|
shardDurations[shard] = clock() - shardStartedAt;
|
|
2521
2689
|
const state = !done.stalled.length ? 'done' as const : done.stalled.some((stall) => stall.reason === 'timeout') ? 'timeout' as const : 'failed' as const;
|
|
2522
2690
|
markShard(shard, { state, rc: done.stalled.length || done.runs.some((run) => run.fail || run.errors) ? 1 : 0, endedAt: new Date().toISOString() }, ['waitReason']);
|
|
@@ -2618,6 +2786,10 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2618
2786
|
return { rc: total.fail + total.errors ? 1 : 0, output: outputs.join('\n') + `\n${total.pass} pass\n${total.fail} fail\n${total.errors} errors\nRan ${total.ran} tests across ${total.files} files.\n`, validatedShards: true,
|
|
2619
2787
|
podSweep: { id: sweepId, shards: shards.length, files: assignable },
|
|
2620
2788
|
passedIds: [...new Set(measured.flatMap((run) => run.passedIds))].sort(),
|
|
2789
|
+
...(measured.some((run) => run.loadFlaky?.length)
|
|
2790
|
+
? { loadFlaky: [...new Set(measured.flatMap((run) => run.loadFlaky ?? []))].sort() } : {}),
|
|
2791
|
+
...(measured.some((run) => run.loadFlakyAttemptedFiles?.length)
|
|
2792
|
+
? { loadFlakyAttemptedFiles: [...new Set(measured.flatMap((run) => run.loadFlakyAttemptedFiles ?? []))].sort() } : {}),
|
|
2621
2793
|
stalledEnv: stalledShards.flatMap((item) => item.files.map((file) => ({ file, reason: 'no-output' as const }))),
|
|
2622
2794
|
...(abortedShards ? { abortedShards } : {}),
|
|
2623
2795
|
...(riskLane ? { riskLane } : {}) };
|
|
@@ -2704,6 +2876,33 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2704
2876
|
await quarantine('gate-job-failed');
|
|
2705
2877
|
throw error;
|
|
2706
2878
|
}
|
|
2879
|
+
if (pod.pinnedImageDigest) {
|
|
2880
|
+
let actualDigest: string | null = null;
|
|
2881
|
+
if (tools.podImageDigest) {
|
|
2882
|
+
actualDigest = tools.podImageDigest({ job, host: producer?.context ?? 'unobserved' });
|
|
2883
|
+
} else if (!poolOverride) {
|
|
2884
|
+
// Rechecks launch through the pool scheduler too; find the Job in its configured
|
|
2885
|
+
// contexts so a recheck cannot silently escape the run-level image pin.
|
|
2886
|
+
const contexts = [...new Set(parsePodPool(pod.pool).map((member) => member.context))];
|
|
2887
|
+
const observed: string[] = [];
|
|
2888
|
+
for (const context of contexts) {
|
|
2889
|
+
try {
|
|
2890
|
+
const got = kubectl(['--context', context, '-n', 'elanous-test', 'get', 'pods', '-l', `job-name=${job.job}`, '-o', 'json']);
|
|
2891
|
+
if (got.status !== 0) continue;
|
|
2892
|
+
const digest = actualGatePodImageDigest(got.stdout);
|
|
2893
|
+
if (digest) observed.push(digest);
|
|
2894
|
+
} catch { /* inability to observe is rejected below */ }
|
|
2895
|
+
}
|
|
2896
|
+
if (new Set(observed).size === 1) actualDigest = observed[0]!;
|
|
2897
|
+
}
|
|
2898
|
+
if (actualDigest !== pod.pinnedImageDigest) {
|
|
2899
|
+
debug.log('release-loop.gate', 'image-pin-mismatch', { runId: pod.pinnedRunId ?? null,
|
|
2900
|
+
job: job.job, phase: 'recheck', host: producer?.context ?? null,
|
|
2901
|
+
pinnedDigest: pod.pinnedImageDigest, actualDigest, decision: 'reject-job' }, { level: 'error' });
|
|
2902
|
+
await quarantine('gate-image-pin-mismatch');
|
|
2903
|
+
throw new Error(`gate image pin mismatch: expected ${pod.pinnedImageDigest}, got ${actualDigest ?? 'unmeasured'} (recheck)`);
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2707
2906
|
try {
|
|
2708
2907
|
const artifact = (name: string) => {
|
|
2709
2908
|
const path = join(job.artifactsDir, name);
|
|
@@ -2848,8 +3047,9 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2848
3047
|
recheckPodBatch: (files, commit, pod, timeoutMs) => runPodRecheckBatch(files, commit, pod, timeoutMs),
|
|
2849
3048
|
get remoteMirror() { return remoteMirror; },
|
|
2850
3049
|
set remoteMirror(path) { remoteMirror = path; },
|
|
2851
|
-
|
|
2852
|
-
|
|
3050
|
+
progressScheduler: tools.progressScheduler ?? scheduleGateProgress,
|
|
3051
|
+
async sweep(tree, logDir, pod, only, onProgress) {
|
|
3052
|
+
if (pod) return podSweep(tree, logDir, pod, only, onProgress);
|
|
2853
3053
|
const listed = await command('git', ['ls-files', '*.test.*'], tree);
|
|
2854
3054
|
check(listed, 'git ls-files tests');
|
|
2855
3055
|
const allFiles = listed.output.split(/\r?\n/).filter((file) => /\.test\.(?:tsx?|jsx?|mts|cts)$/.test(file));
|
|
@@ -2871,13 +3071,26 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2871
3071
|
} else cdpPatterns = deriveCdpTestPatterns({ cwd: tree });
|
|
2872
3072
|
const outputs: string[] = [];
|
|
2873
3073
|
let total = { pass: 0, fail: 0, errors: 0, ran: 0, files: 0 };
|
|
2874
|
-
|
|
2875
|
-
|
|
3074
|
+
const selectedGroups = groups.filter((group) => group.paths.length && files.some((f) => group.paths.some((p) => f === p || f.startsWith(`${p}/`))));
|
|
3075
|
+
const progressGroups: GateProgressGroup[] = selectedGroups.map((group) => ({ id: group.name, state: 'pending', ...(remote ? { host: remote } : {}) }));
|
|
3076
|
+
onProgress?.({ executor: 'host', groups: progressGroups });
|
|
3077
|
+
for (const group of selectedGroups) {
|
|
3078
|
+
const progress = progressGroups.find((item) => item.id === group.name)!;
|
|
3079
|
+
progress.state = 'running'; progress.startedAtMs = clock();
|
|
3080
|
+
onProgress?.({ executor: 'host', groups: progressGroups });
|
|
2876
3081
|
const start = Date.now();
|
|
2877
3082
|
const groupIgnores = [...new Set(cdpPatterns)]
|
|
2878
3083
|
.filter((pattern) => files.includes(pattern) && group.paths.some((p) => pattern === p || pattern.startsWith(`${p}/`)))
|
|
2879
3084
|
.flatMap((pattern) => ['--path-ignore-patterns', pattern]);
|
|
2880
|
-
|
|
3085
|
+
let run: CommandResult;
|
|
3086
|
+
try { run = await command('bun', ['run', 'test:deterministic', ...groupIgnores, ...group.paths.map(asPath)], tree); }
|
|
3087
|
+
catch (error) {
|
|
3088
|
+
progress.state = 'failed'; progress.endedAtMs = clock();
|
|
3089
|
+
onProgress?.({ executor: 'host', groups: progressGroups });
|
|
3090
|
+
throw error;
|
|
3091
|
+
}
|
|
3092
|
+
progress.state = run.timedOut ? 'timeout' : run.rc === 0 ? 'done' : 'failed'; progress.endedAtMs = clock();
|
|
3093
|
+
onProgress?.({ executor: 'host', groups: progressGroups });
|
|
2881
3094
|
if (logDir) {
|
|
2882
3095
|
mkdirSync(logDir, { recursive: true });
|
|
2883
3096
|
writeFileSync(join(logDir, `${group.name}.log`), run.output, { mode: 0o600 });
|
|
@@ -3009,7 +3222,8 @@ export function saveBaselineFileCache(ledger: string, commit: string, rows: Base
|
|
|
3009
3222
|
}
|
|
3010
3223
|
|
|
3011
3224
|
export async function judgeGate(opts: GateOptions, runner: GateRunner = createGateRunner(opts.repo ?? process.cwd(), opts.remote),
|
|
3012
|
-
imageProbe: typeof preflightGateImageDigest = preflightGateImageDigest
|
|
3225
|
+
imageProbe: typeof preflightGateImageDigest = preflightGateImageDigest,
|
|
3226
|
+
identityProbe: typeof preflightGateExecutionIdentity = preflightGateExecutionIdentity): Promise<GateResult> {
|
|
3013
3227
|
if (opts.pod?.warmReplay) opts = { ...opts, pod: { ...opts.pod,
|
|
3014
3228
|
warmShadowScope: { requested: false, attempts: 0 } } };
|
|
3015
3229
|
const explicitConfig = getElanousConfigDirOverride();
|
|
@@ -3056,18 +3270,88 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
3056
3270
|
if (!sha.test(opts.commit) || !versionPattern.test(opts.version)
|
|
3057
3271
|
|| (opts.baselineVersion && !versionPattern.test(opts.baselineVersion))
|
|
3058
3272
|
|| (opts.baselineCommit && !sha.test(opts.baselineCommit))) throw new Error('invalid commit or version');
|
|
3059
|
-
|
|
3273
|
+
const baselineVersion = opts.baselineVersion;
|
|
3274
|
+
if (!baselineVersion) throw new Error('previous release version required (--baseline-version)');
|
|
3060
3275
|
if (opts.rehearsalBaseline && (opts.graphRunId || !opts.pod || !/-rc\.\d+$/.test(opts.version)))
|
|
3061
3276
|
throw new Error('rehearsal baseline requires a direct Pod gate with an isolated rc version');
|
|
3062
3277
|
// Observation only. Failure to plan cannot change the gate selector or verdict.
|
|
3063
3278
|
try { radiusShadow = planGateRadiusShadow({ repo, ledgerRoot: ledger, version: opts.version,
|
|
3064
|
-
baselineVersion
|
|
3279
|
+
baselineVersion, forCommit: opts.commit, instanceRoot: root,
|
|
3065
3280
|
expectedBaselineCommit: opts.baselineCommit }); }
|
|
3066
3281
|
catch (error) { debug.log('release-loop.gate', 'radius-shadow-unavailable', { reason: String(error) }); }
|
|
3067
3282
|
if (!Number.isSafeInteger(opts.recheckFileLimit ?? GATE_HOST_RECHECK_FILE_LIMIT) || (opts.recheckFileLimit ?? GATE_HOST_RECHECK_FILE_LIMIT) < 1)
|
|
3068
3283
|
throw new Error('invalid gate recheck file limit');
|
|
3069
3284
|
if (opts.pod && (!opts.pod.pool || !Number.isSafeInteger(opts.pod.shards ?? 24) || (opts.pod.shards ?? 24) < 1
|
|
3070
3285
|
|| !Number.isSafeInteger(opts.pod.shardTimeoutSeconds ?? 1200) || (opts.pod.shardTimeoutSeconds ?? 1200) < 1)) throw new Error('invalid pod sweep options');
|
|
3286
|
+
if (opts.pod && opts.graphRunId) {
|
|
3287
|
+
const components = opts.pod.pool.split(',').map((part) => part.trim()).filter(Boolean);
|
|
3288
|
+
const members = parsePodPool(opts.pod.pool);
|
|
3289
|
+
const memberSpecs = members.map((member) => {
|
|
3290
|
+
const spec = components.find((part) => part.split(/[@:#]/, 1)[0] === member.context);
|
|
3291
|
+
if (!spec) throw new Error(`gate image pin cannot map pool member ${member.context}`);
|
|
3292
|
+
return { context: member.context, spec };
|
|
3293
|
+
});
|
|
3294
|
+
const poolImages = new Map<string, readonly string[]>();
|
|
3295
|
+
const currentImages: Record<string, string | null> = {};
|
|
3296
|
+
for (const member of memberSpecs) {
|
|
3297
|
+
const digest = imageProbe(member.spec, opts.commit);
|
|
3298
|
+
currentImages[member.context] = digest;
|
|
3299
|
+
poolImages.set(member.context, digest ? [digest] : []);
|
|
3300
|
+
}
|
|
3301
|
+
const pinRunId = opts.graphRunId;
|
|
3302
|
+
const pinPath = gateImagePinPath(root, opts.version, pinRunId);
|
|
3303
|
+
const gateImageTag = `${podCommandImageFor([], true).split(':')[0]}:${opts.commit.slice(0, 12)}`;
|
|
3304
|
+
let savedPin: ReturnType<typeof readGateImagePin>;
|
|
3305
|
+
try { savedPin = existsSync(pinPath) ? readGateImagePin(pinPath, pinRunId, gateImageTag, opts.commit) : null; }
|
|
3306
|
+
catch (error) {
|
|
3307
|
+
debug.log('release-loop.gate', 'image-pin', { runId: pinRunId, currentImages, decision: 'fail-closed',
|
|
3308
|
+
reason: 'saved-image-pin-invalid', error: String(error).slice(0, 300) }, { level: 'error' });
|
|
3309
|
+
throw new Error('gate resume refused: saved image digest pin is invalid');
|
|
3310
|
+
}
|
|
3311
|
+
const savedDigest = savedPin?.digest ?? null;
|
|
3312
|
+
let previouslyExecuted = false;
|
|
3313
|
+
if (opts.graphRunId) {
|
|
3314
|
+
// A gate's immutable pin is written before any Pod work. The graph state is only a legacy-run detector:
|
|
3315
|
+
// if it already records an executed gate but this pin does not exist, resume cannot safely invent one.
|
|
3316
|
+
const runPath = join(root, 'graph-runs', 'release-loop', `${opts.graphRunId}.json`);
|
|
3317
|
+
if (existsSync(runPath)) {
|
|
3318
|
+
try {
|
|
3319
|
+
const prior = JSON.parse(readFileSync(runPath, 'utf8')) as { nodes?: Array<{ nodeId?: unknown; executed?: unknown }> };
|
|
3320
|
+
previouslyExecuted = prior.nodes?.some((node) => node.nodeId === 'gate' && node.executed === true) ?? false;
|
|
3321
|
+
} catch { previouslyExecuted = true; }
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3324
|
+
if (!savedDigest && previouslyExecuted) {
|
|
3325
|
+
debug.log('release-loop.gate', 'image-pin', { runId: opts.graphRunId, savedDigest: null, currentImages,
|
|
3326
|
+
decision: 'fail-closed', reason: 'resume-pin-missing' }, { level: 'error' });
|
|
3327
|
+
throw new Error('gate resume refused: saved image digest pin is missing');
|
|
3328
|
+
}
|
|
3329
|
+
const resolution = resolveGateImagePin({ ...(savedDigest ? { savedDigest } : {}),
|
|
3330
|
+
...(!savedDigest ? { initialDigest: [...poolImages.values()].flat().find(Boolean) ?? null } : {}), poolImages });
|
|
3331
|
+
if (!resolution.ok) {
|
|
3332
|
+
debug.log('release-loop.gate', 'image-pin', { runId: opts.graphRunId ?? null, savedDigest, currentImages,
|
|
3333
|
+
decision: 'fail-closed', reason: resolution.reason }, { level: 'error' });
|
|
3334
|
+
throw new Error(`gate image pin refused: ${resolution.reason}`);
|
|
3335
|
+
}
|
|
3336
|
+
const selected = memberSpecs.filter((member) => resolution.eligiblePools.includes(member.context));
|
|
3337
|
+
if (!selected.length) throw new Error('gate image pin refused: no eligible pool');
|
|
3338
|
+
if (!savedDigest) {
|
|
3339
|
+
try { writeGateImagePin(pinPath, pinRunId, resolution.digest, gateImageTag, opts.commit); }
|
|
3340
|
+
catch (error) {
|
|
3341
|
+
debug.log('release-loop.gate', 'image-pin', { runId: pinRunId, pinnedDigest: resolution.digest,
|
|
3342
|
+
currentImages, decision: 'fail-closed', reason: 'pin-write-failed', error: String(error).slice(0, 300) }, { level: 'error' });
|
|
3343
|
+
throw error;
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
opts = { ...opts, pod: { ...opts.pod, pool: selected.map((member) => member.spec).join(','),
|
|
3347
|
+
pinnedImageDigest: resolution.digest, pinnedRunId: pinRunId } };
|
|
3348
|
+
debug.log('release-loop.gate', 'image-pin', { runId: opts.graphRunId ?? null, pinnedDigest: resolution.digest,
|
|
3349
|
+
currentImages, eligiblePools: resolution.eligiblePools, excludedPools: members.map((member) => member.context)
|
|
3350
|
+
.filter((context) => !resolution.eligiblePools.includes(context)), decision: savedDigest ? 'resume-pinned' : 'pin-created' });
|
|
3351
|
+
} else if (opts.pod) {
|
|
3352
|
+
debug.log('release-loop.gate', 'image-pin-unscoped', { runId: null, decision: 'not-applicable',
|
|
3353
|
+
reason: 'direct-pod-gate-without-graph-run-id' });
|
|
3354
|
+
}
|
|
3071
3355
|
if (opts.remote && !/^(?:[\w.-]+@)?[\w.-]+$/.test(opts.remote)) throw new Error('invalid ssh host');
|
|
3072
3356
|
if (opts.remote) {
|
|
3073
3357
|
runner.remoteMirror = undefined;
|
|
@@ -3078,8 +3362,8 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
3078
3362
|
const homePath = home?.output.split(/\r?\n/).find((line) => line.startsWith('/'));
|
|
3079
3363
|
if (home && (!homePath || !/^\/[^\r\n]*$/.test(homePath))) throw new Error('invalid remote home');
|
|
3080
3364
|
const mirror = configured.startsWith('~/') ? join(homePath!, configured.slice(2)) : configured;
|
|
3081
|
-
const baseSha = opts.baselineCommit ?? baselineReleaseCommit(
|
|
3082
|
-
cachedBaseline(
|
|
3365
|
+
const baseSha = opts.baselineCommit ?? baselineReleaseCommit(baselineVersion);
|
|
3366
|
+
cachedBaseline(baselineVersion, baseSha);
|
|
3083
3367
|
const present = await runner.command('test', ['-d', mirror], '/tmp');
|
|
3084
3368
|
if (present.rc === 1) {
|
|
3085
3369
|
check(await runner.command('mkdir', ['-p', dirname(mirror)], '/tmp'), 'remote mirror parent');
|
|
@@ -3108,26 +3392,57 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
3108
3392
|
// GATE-PARTIAL: a plan made by `release resume --from gate --partial` for exactly this commit re-runs only its files.
|
|
3109
3393
|
// Its logs go to cut-partial/ — the full cut/ logs stay whole (the next release reads them as a baseline source).
|
|
3110
3394
|
const partialRead = readPartialPlanResult(roots, opts.version, opts.commit);
|
|
3111
|
-
const explicitPartial = partialRead.plan;
|
|
3395
|
+
const explicitPartial = opts.full ? null : partialRead.plan;
|
|
3112
3396
|
const rehearsalStampPresent = ['hard-5h', 'hard-mid'].some((step) =>
|
|
3113
3397
|
existsSync(join(root, 'release', opts.version, 'rehearsal', `${step}.json`)));
|
|
3114
|
-
const
|
|
3115
|
-
?
|
|
3398
|
+
const currentIdentity = !opts.full && !explicitPartial && opts.graphRunId && opts.pod && rehearsalStampPresent
|
|
3399
|
+
? identityProbe(opts.pod.pool, process.env, opts.commit) : null;
|
|
3400
|
+
const currentImage = currentIdentity?.imageDigest ?? (!opts.full && !explicitPartial
|
|
3401
|
+
&& opts.graphRunId && opts.pod && rehearsalStampPresent ? imageProbe(opts.pod.pool, opts.commit) : null);
|
|
3116
3402
|
const receiptImage = currentImage ?? (opts.graphRunId && opts.pod && process.env.ELANOUS_GRAPH_CONTEXT
|
|
3117
|
-
? imageProbe(opts.pod.pool) : null);
|
|
3118
|
-
const receiptBase = opts.baselineCommit ?? (
|
|
3119
|
-
existsSync(join(location, 'release',
|
|
3120
|
-
? baselineReleaseCommit(
|
|
3121
|
-
const radius = !explicitPartial && opts.graphRunId && opts.pod && rehearsalStampPresent
|
|
3403
|
+
? imageProbe(opts.pod.pool, opts.commit) : null);
|
|
3404
|
+
const receiptBase = opts.baselineCommit ?? (roots.some((location) =>
|
|
3405
|
+
existsSync(join(location, 'release', baselineVersion, 'release.json')))
|
|
3406
|
+
? baselineReleaseCommit(baselineVersion) : undefined);
|
|
3407
|
+
const radius = !opts.full && !explicitPartial && opts.graphRunId && opts.pod && rehearsalStampPresent
|
|
3122
3408
|
? planHardRehearsalRadius({ root, repo, version: opts.version, forCommit: opts.commit,
|
|
3123
|
-
graphPins: gateGraphPins(root, opts.graphRunId), envFingerprint: gateEnvFingerprint(process.env), imageDigest: currentImage
|
|
3409
|
+
graphPins: gateGraphPins(root, opts.graphRunId), envFingerprint: gateEnvFingerprint(process.env), imageDigest: currentImage,
|
|
3410
|
+
executionIdentity: currentIdentity })
|
|
3124
3411
|
: null;
|
|
3125
3412
|
if (radius) debug.log('release-loop.gate', 'radius-live-plan', { version: opts.version, reason: radius.reason,
|
|
3126
3413
|
files: radius.plan?.files.length ?? null, rehearsalRunId: radius.rehearsalRunId ?? null });
|
|
3127
|
-
let partial = explicitPartial ?? radius?.plan ?? null;
|
|
3414
|
+
let partial = opts.full ? null : explicitPartial ?? radius?.plan ?? null;
|
|
3415
|
+
let trainReceiptPlan: ReturnType<typeof planGateTrainReceiptCarry> = null;
|
|
3416
|
+
if (!opts.full && !partial && opts.graphRunId && opts.pod) {
|
|
3417
|
+
const receiptPath = gateReceiptRegistryPath(repo);
|
|
3418
|
+
const bun = spawnSync('bun', ['--version'], { cwd: repo, encoding: 'utf8', maxBuffer: SPAWN_SYNC_MAX_BUFFER });
|
|
3419
|
+
if (receiptPath && bun.status === 0 && !bun.error) {
|
|
3420
|
+
try {
|
|
3421
|
+
trainReceiptPlan = planGateTrainReceiptCarry({ repo, commit: opts.commit, receiptPath,
|
|
3422
|
+
bunVersion: bun.stdout.trim(), lockfileHash: gateLockfileHash(repo), envFingerprint: gateReceiptEnvFingerprint(process.env) });
|
|
3423
|
+
if (trainReceiptPlan?.carriedFiles.length) partial = { version: opts.version, priorCommit: trainReceiptPlan.priorCommit,
|
|
3424
|
+
forCommit: opts.commit, files: trainReceiptPlan.files, priorFailures: [], priorErrors: [] };
|
|
3425
|
+
} catch (error) {
|
|
3426
|
+
debug.log('release-loop.gate', 'train-receipt-plan-failed', { version: opts.version, reason: String(error).slice(0, 240) });
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
if (trainReceiptPlan) {
|
|
3430
|
+
result.trainReceiptReuse = { carried: trainReceiptPlan.carriedFiles.length, total: trainReceiptPlan.totalFiles,
|
|
3431
|
+
receipts: trainReceiptPlan.receipts.length, reasons: trainReceiptPlan.reasons };
|
|
3432
|
+
debug.log('release-loop.gate', 'train-receipt-plan', { version: opts.version, commit: opts.commit,
|
|
3433
|
+
carried: trainReceiptPlan.carriedFiles.length, total: trainReceiptPlan.totalFiles,
|
|
3434
|
+
ratio: trainReceiptPlan.carriedFiles.length / trainReceiptPlan.totalFiles,
|
|
3435
|
+
receipts: trainReceiptPlan.receipts, selectedFiles: trainReceiptPlan.files.length,
|
|
3436
|
+
reasons: trainReceiptPlan.reasons, action: trainReceiptPlan.carriedFiles.length ? 'run-remaining-tests' : 'full-sweep' });
|
|
3437
|
+
} else if (!opts.full && !explicitPartial && !radius?.plan) {
|
|
3438
|
+
debug.log('release-loop.gate', 'train-receipt-plan', { version: opts.version, commit: opts.commit,
|
|
3439
|
+
carried: 0, total: null, ratio: 0, receipts: [], selectedFiles: null,
|
|
3440
|
+
reasons: ['no safe receipt coverage'], action: 'full-sweep' });
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3128
3443
|
const explicitPodPlan = !!explicitPartial && explicitPartial.kind !== 'recheck' && !!opts.pod;
|
|
3129
3444
|
const explicitPodDigest = explicitPodPlan ? explicitPartial!.shardImageDigests?.[0] : undefined;
|
|
3130
|
-
const probedPodDigest = explicitPodDigest ? imageProbe(opts.pod!.pool) : null;
|
|
3445
|
+
const probedPodDigest = explicitPodDigest ? imageProbe(opts.pod!.pool, opts.commit) : null;
|
|
3131
3446
|
if (explicitPodPlan && (!explicitPodDigest || probedPodDigest !== explicitPodDigest)) {
|
|
3132
3447
|
debug.log('release-loop.gate', 'partial-image-preflight-fallback', { version: opts.version,
|
|
3133
3448
|
reason: !explicitPodDigest || !probedPodDigest ? 'executed-pod-image-digest-missing' : 'executed-pod-image-digest-mismatch' }, { level: 'warn' });
|
|
@@ -3163,15 +3478,33 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
3163
3478
|
let knownFailures: string[] = [];
|
|
3164
3479
|
if (opts.pod) {
|
|
3165
3480
|
try {
|
|
3166
|
-
const knownSha = opts.baselineCommit ?? baselineReleaseCommit(
|
|
3167
|
-
const saved = cachedBaseline(
|
|
3168
|
-
const recorded = saved ? undefined : baselineFromCutLogs(join(ledger, 'release',
|
|
3481
|
+
const knownSha = opts.baselineCommit ?? baselineReleaseCommit(baselineVersion);
|
|
3482
|
+
const saved = cachedBaseline(baselineVersion, knownSha)?.saved;
|
|
3483
|
+
const recorded = saved ? undefined : baselineFromCutLogs(join(ledger, 'release', baselineVersion, 'gate-logs', 'cut'), knownSha);
|
|
3169
3484
|
const trustedKnown = saved ?? (recorded?.complete ? recorded : undefined);
|
|
3170
3485
|
if (trustedKnown) knownFailures = [...trustedKnown.failures, ...(trustedKnown.errors ?? []), ...loadEnvKnownFailures(repo)];
|
|
3171
3486
|
} catch (error) { debug.log('release-loop.gate', 'known-failures-unavailable', { error: String(error) }); }
|
|
3172
3487
|
}
|
|
3173
|
-
const durationSource = join(ledger, 'release',
|
|
3174
|
-
const runCut = (selected: typeof partial) =>
|
|
3488
|
+
const durationSource = join(ledger, 'release', baselineVersion, 'gate-logs', 'cut');
|
|
3489
|
+
const runCut = async (selected: typeof partial) => {
|
|
3490
|
+
if (selected?.kind === 'carry') return undefined;
|
|
3491
|
+
const progressFile = opts.graphRunId && /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(opts.graphRunId)
|
|
3492
|
+
? join(ledger, 'graph-runs', 'release-loop', `${opts.graphRunId}.json.contexts`, 'gate-progress.jsonl') : undefined;
|
|
3493
|
+
let latest: GateSweepProgress | undefined;
|
|
3494
|
+
const emitProgress = () => {
|
|
3495
|
+
if (!latest) return;
|
|
3496
|
+
const nowMs = runner.now?.() ?? Date.now();
|
|
3497
|
+
const event = { version: opts.version, runId: opts.graphRunId ?? null, at: new Date(nowMs).toISOString(),
|
|
3498
|
+
...summarizeGateProgress({ executor: latest.executor, groups: latest.groups, nowMs }) };
|
|
3499
|
+
debug.log('release-loop.gate', 'progress', event);
|
|
3500
|
+
if (progressFile) {
|
|
3501
|
+
try { mkdirSync(dirname(progressFile), { recursive: true }); appendFileSync(progressFile, `${JSON.stringify(event)}\n`, { mode: 0o600 }); }
|
|
3502
|
+
catch (error) { debug.log('release-loop.gate', 'progress-log-write-failed', { path: progressFile, error: String(error) }, { level: 'warn' }); }
|
|
3503
|
+
}
|
|
3504
|
+
};
|
|
3505
|
+
const cancel = (runner.progressScheduler ?? scheduleGateProgress)(emitProgress, 60_000);
|
|
3506
|
+
try {
|
|
3507
|
+
const sweepResult = await runner.sweep(cutTree,
|
|
3175
3508
|
join(root, 'release', opts.version, 'gate-logs', selected ? 'cut-partial' : 'cut'),
|
|
3176
3509
|
opts.pod ? { ...opts.pod, durationSource, ...(knownFailures.length ? { knownFailures } : {}), onSelectionTimed: ({ ms }) => { result.selectionMs = ms; },
|
|
3177
3510
|
// GATE-RISK-LANE — the watch list is read from this gate's own ledgers (the instance root ⊕ the machine ledger);
|
|
@@ -3184,7 +3517,11 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
3184
3517
|
receiptRoot: join(ledger, 'release', opts.version, 'gate-logs', 'cut', 'receipts'),
|
|
3185
3518
|
base40: receiptBase,
|
|
3186
3519
|
expectedImageDigest: receiptImage } } : undefined,
|
|
3187
|
-
selected?.files);
|
|
3520
|
+
selected?.files, (progress) => { latest = progress; emitProgress(); });
|
|
3521
|
+
emitProgress();
|
|
3522
|
+
return sweepResult;
|
|
3523
|
+
} finally { cancel?.(); }
|
|
3524
|
+
};
|
|
3188
3525
|
let cutRun = await runCut(partial);
|
|
3189
3526
|
const radiusSelectionMatched = partial?.files && cutRun?.podSweep?.files
|
|
3190
3527
|
&& JSON.stringify([...partial.files].sort()) === JSON.stringify([...cutRun.podSweep.files].sort());
|
|
@@ -3248,7 +3585,45 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
3248
3585
|
debug.log('release-loop.gate', 'pod-shard-stalled-env', { files: stalled.map(({ file, reason }) => ({ file, reason })), local });
|
|
3249
3586
|
return measured;
|
|
3250
3587
|
};
|
|
3251
|
-
|
|
3588
|
+
let measuredCut: SweepFailures;
|
|
3589
|
+
try {
|
|
3590
|
+
measuredCut = cutRun ? await localStalled(cutRun, cutTree, opts.commit, 'cut') : { failures: [], errors: [] };
|
|
3591
|
+
} catch (error) {
|
|
3592
|
+
if (!(error instanceof GateFailureAttributionMismatch) || error.label !== 'cut sweep') throw error;
|
|
3593
|
+
const targetFiles = [...new Set(error.failures.map(fileOf))].sort();
|
|
3594
|
+
const attribution = { summary: error.summary, observed: error.observed, identified: error.identified,
|
|
3595
|
+
collapsed: error.collapsed, targetFiles,
|
|
3596
|
+
baselineResult: 'failed' as 'completed' | 'failed' | 'timeout' };
|
|
3597
|
+
result.attributionRecheck = attribution;
|
|
3598
|
+
debug.log('release-loop.gate', 'failure-attribution-baseline-recheck', { ...attribution, phase: 'started' });
|
|
3599
|
+
try {
|
|
3600
|
+
if (!targetFiles.length || targetFiles.length > GATE_ATTRIBUTION_RECHECK_FILE_LIMIT)
|
|
3601
|
+
throw new Error(`baseline recheck target count ${targetFiles.length} outside safe bound 1..${GATE_ATTRIBUTION_RECHECK_FILE_LIMIT}`);
|
|
3602
|
+
if (!work) throw new Error('gate workspace missing before baseline attribution recheck');
|
|
3603
|
+
const recheckTree = join(work, 'baseline-attribution-recheck');
|
|
3604
|
+
trees.push(recheckTree);
|
|
3605
|
+
const baseShaForRecheck = opts.baselineCommit ?? baselineReleaseCommit(baselineVersion);
|
|
3606
|
+
await runner.add(recheckTree, baseShaForRecheck);
|
|
3607
|
+
for (const dir of [recheckTree, join(recheckTree, 'apps/pwa')])
|
|
3608
|
+
check(await runner.command('bun', ['install'], dir), `bun install ${dir}`);
|
|
3609
|
+
const rechecked = await runner.sweep(recheckTree,
|
|
3610
|
+
join(root, 'release', opts.version, 'gate-logs', 'baseline-attribution-recheck'),
|
|
3611
|
+
opts.pod ? { ...opts.pod, durationSource } : undefined,
|
|
3612
|
+
targetFiles);
|
|
3613
|
+
if (rechecked.timedOut || rechecked.abortedShards || rechecked.stalledEnv?.length) {
|
|
3614
|
+
attribution.baselineResult = 'timeout';
|
|
3615
|
+
throw new Error('baseline sweep timed out or stalled');
|
|
3616
|
+
}
|
|
3617
|
+
failuresOf(rechecked, 'baseline attribution recheck');
|
|
3618
|
+
attribution.baselineResult = 'completed';
|
|
3619
|
+
} catch (recheckError) {
|
|
3620
|
+
debug.log('release-loop.gate', 'failure-attribution-baseline-recheck', { ...attribution, phase: 'failed', error: String(recheckError) });
|
|
3621
|
+
throw new Error(`baseline recheck ${attribution.baselineResult}; cut attribution unresolved (summary=${attribution.summary}, identified=${attribution.identified})`);
|
|
3622
|
+
}
|
|
3623
|
+
debug.log('release-loop.gate', 'failure-attribution-baseline-recheck', { ...attribution, phase: 'completed',
|
|
3624
|
+
disposition: 'cut names remain unresolved; baseline success cannot identify unnamed cut failures; fail closed' });
|
|
3625
|
+
throw new Error(`baseline recheck completed; cut attribution still unresolved (summary=${attribution.summary}, identified=${attribution.identified})`);
|
|
3626
|
+
}
|
|
3252
3627
|
// A between-tests error proves only that the shared process failed. Re-run each affected file by itself using the
|
|
3253
3628
|
// same isolated path as ordinary new-failure rechecks, even when the error was present in the baseline ledger.
|
|
3254
3629
|
// Replace that file's shard observations with its isolated observations before the existing regression comparison.
|
|
@@ -3289,15 +3664,30 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
3289
3664
|
}
|
|
3290
3665
|
}
|
|
3291
3666
|
const cut = partial ? mergePartialFailures(partial, measuredCut) : measuredCut;
|
|
3667
|
+
const samePodFlaky = [...new Set(cutRun?.loadFlaky ?? [])].sort();
|
|
3668
|
+
if (samePodFlaky.length) {
|
|
3669
|
+
const flaky = new Set(samePodFlaky);
|
|
3670
|
+
cut.failures = cut.failures.filter((id) => !flaky.has(id));
|
|
3671
|
+
cut.errors = cut.errors.filter((id) => !flaky.has(id));
|
|
3672
|
+
result.loadFlaky = [...new Set([...(result.loadFlaky ?? []), ...samePodFlaky])].sort();
|
|
3673
|
+
debug.log('release-loop.gate', 'same-pod-load-flaky', { ids: samePodFlaky, files: [...new Set(samePodFlaky.map(fileOf))].sort() });
|
|
3674
|
+
try {
|
|
3675
|
+
const path = loadFlakyCensusPath(ledger);
|
|
3676
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
3677
|
+
const at = new Date().toISOString();
|
|
3678
|
+
appendFileSync(path, samePodFlaky.map((id) => JSON.stringify({ at, version: opts.version, commit: opts.commit, id,
|
|
3679
|
+
host: opts.pod ? `pod:${opts.pod.pool}` : opts.remote ?? 'local', retry: 'same-pod-standalone' }) + '\n').join(''), { mode: 0o600 });
|
|
3680
|
+
} catch (error) { debug.log('release-loop.gate', 'load-flaky-census-write-failed', { ids: samePodFlaky, error: String(error) }); }
|
|
3681
|
+
}
|
|
3292
3682
|
if (partial) result.partial = { priorCommit: partial.priorCommit, files: partial.files.length };
|
|
3293
3683
|
cutFailures = cut;
|
|
3294
|
-
const baseSha = opts.baselineCommit ?? baselineReleaseCommit(
|
|
3684
|
+
const baseSha = opts.baselineCommit ?? baselineReleaseCommit(baselineVersion);
|
|
3295
3685
|
baselineCommitForVerdict = baseSha;
|
|
3296
|
-
const cached = cachedBaseline(
|
|
3297
|
-
const recorded = cached ? undefined : baselineFromCutLogs(join(ledger, 'release',
|
|
3686
|
+
const cached = cachedBaseline(baselineVersion, baseSha);
|
|
3687
|
+
const recorded = cached ? undefined : baselineFromCutLogs(join(ledger, 'release', baselineVersion, 'gate-logs', 'cut'), baseSha);
|
|
3298
3688
|
const trusted = cached?.saved ?? (recorded?.complete ? recorded : undefined);
|
|
3299
3689
|
result.baselineSource = cached ? cached.source : recorded?.complete ? 'cut-logs' : 'swept';
|
|
3300
|
-
debug.log('release-loop.gate', 'baseline', { version:
|
|
3690
|
+
debug.log('release-loop.gate', 'baseline', { version: baselineVersion, source: result.baselineSource, commit: baseSha });
|
|
3301
3691
|
let baseTree: string | undefined;
|
|
3302
3692
|
const getBaseTree = async () => {
|
|
3303
3693
|
if (!baseTree) {
|
|
@@ -3328,7 +3718,7 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
3328
3718
|
present = cutFiles.filter((file) => known.has(file));
|
|
3329
3719
|
}
|
|
3330
3720
|
result.baselineDeferred = { cutFiles: cutFiles.length, swept: present.length };
|
|
3331
|
-
debug.log('release-loop.gate', 'baseline-deferred', { version:
|
|
3721
|
+
debug.log('release-loop.gate', 'baseline-deferred', { version: baselineVersion, commit: baseSha, cutFiles: cutFiles.length, swept: present.length });
|
|
3332
3722
|
if (present.length) {
|
|
3333
3723
|
// K9b — the baseline is swept on the same Pod pool as the cut, not on this host (0.2.6: an hour of local baseline
|
|
3334
3724
|
// after a 23-minute Pod cut).
|
|
@@ -3432,7 +3822,8 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
3432
3822
|
// per-test limit, on the same runner (node-b when the gate is remote). Passing there = flaky under load (warning);
|
|
3433
3823
|
// an assertion failure, a failure again, or a run that cannot be read stays introduced.
|
|
3434
3824
|
const timedOut = cutCheck === undefined ? new Set<string>() : new Set(parseTimedOutFailures(stripPodRepoRoot(cutCheck.output)));
|
|
3435
|
-
const
|
|
3825
|
+
const samePodRetried = new Set(cutRun?.loadFlakyAttemptedFiles ?? []);
|
|
3826
|
+
const shaped = candidates.filter((id) => timedOut.has(id) && isolatedCut.failures.includes(id) && !samePodRetried.has(fileOf(id)));
|
|
3436
3827
|
if (shaped.length) {
|
|
3437
3828
|
const relaxed = opts.pod && runner.recheckPod
|
|
3438
3829
|
? await runner.recheckPod(file, opts.commit, { ...opts.pod, warmCutSha: opts.commit, warmBaselineSha: baseSha,
|
|
@@ -3651,11 +4042,13 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
3651
4042
|
...(result.interferenceErrors?.length ? { interferenceErrors: [...result.interferenceErrors].sort() } : {}),
|
|
3652
4043
|
};
|
|
3653
4044
|
const observed = { ...(result.selectionMs !== undefined ? { selectionMs: result.selectionMs } : {}),
|
|
3654
|
-
...(result.warmShadow?.length ? { warmShadow: result.warmShadow } : {})
|
|
4045
|
+
...(result.warmShadow?.length ? { warmShadow: result.warmShadow } : {}),
|
|
4046
|
+
...(result.attributionRecheck ? { attributionRecheck: result.attributionRecheck } : {}) };
|
|
3655
4047
|
// Receipt is advisory for a later radius gate. Missing identity makes reuse invalid, never this gate's verdict.
|
|
3656
4048
|
const baselineReceipt = gateBaselineReceipt({ root, version: opts.version, commit: opts.commit,
|
|
3657
4049
|
graphPins: gateGraphPins(root, opts.graphRunId) ?? (opts.rehearsalBaseline ? gateSourcePins(repo, opts.commit) : null),
|
|
3658
4050
|
envFingerprint: gateEnvFingerprint(process.env),
|
|
4051
|
+
...(result.attributionRecheck ? { attributionRecheck: result.attributionRecheck } : {}),
|
|
3659
4052
|
fullSweep: result.outcome === 'ok' && cutSweepUsable && !result.partial && !!cutFailures
|
|
3660
4053
|
&& !result.rcNonzeroNoFailures.length && !result.stalledShards?.length && !result.stalledEnv.length
|
|
3661
4054
|
&& !result.unmeasuredRecheckFiles?.length && !result.unmeasuredErrors?.length });
|
|
@@ -3736,7 +4129,7 @@ export function graphGateResult(result: GateResult, graph: boolean) {
|
|
|
3736
4129
|
summary: (result.partial ? `부분 재검 ${result.partial.files}파일(앞 게이트 ${result.partial.priorCommit.slice(0, 9)} 결과 이음) · ` : '')
|
|
3737
4130
|
+ (result.outcome === 'ok' ? `새 회귀 ${result.introduced.length} · 기존 ${result.preexisting} · 고침 ${result.fixed ?? '못 셈(기준선 미루기)'}` : `게이트 ${result.outcome}: ${result.error ?? result.introduced.length + ' new regressions'}`) + ` · 환경 알려진 실패 ${result.knownEnv}`
|
|
3738
4131
|
+ (result.rcNonzeroNoFailures.length ? ` · ⚠ 종료코드만 비영 ${result.rcNonzeroNoFailures.length}` : '')
|
|
3739
|
-
+ (result.loadFlaky?.length ? ` · ⚠ 부하 흔들림 ${result.loadFlaky.length}(
|
|
4132
|
+
+ (result.loadFlaky?.length ? ` · ⚠ 부하 흔들림 ${result.loadFlaky.length}(독립 재실행 통과 · 막지 않음)` : '')
|
|
3740
4133
|
+ (result.stalledEnv.length ? ` · 환경 멈춤 ${result.stalledEnv.length} (Pod 밖 통과 ${result.stalledEnv.filter((item) => item.local === 'passed').length})` : '')
|
|
3741
4134
|
+ (result.abortedShards ? ` · 중단 조각 ${result.abortedShards}(중간 매달림 · 시험만 끊고 재확인)` : '') };
|
|
3742
4135
|
}
|
|
@@ -3827,6 +4220,7 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
|
|
|
3827
4220
|
const arg = args[i];
|
|
3828
4221
|
if (arg === '--help') return 'help';
|
|
3829
4222
|
if (arg === '--json') continue;
|
|
4223
|
+
if (arg === '--full') { opts.full = true; continue; }
|
|
3830
4224
|
if (arg === '--rehearsal-baseline') { opts.rehearsalBaseline = true; continue; }
|
|
3831
4225
|
// 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).
|
|
3832
4226
|
if (arg === '--fresh-shards') { freshShards = true; continue; }
|
|
@@ -3889,10 +4283,13 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
|
|
|
3889
4283
|
/** The node's run: judge, emit the graph result line, and return the process exit code (ok 0 · regression 1 · else 2).
|
|
3890
4284
|
* `runner` is a seam for tests; the process entry passes none. */
|
|
3891
4285
|
export async function runGateNode(argv: string[], env: NodeJS.ProcessEnv, runner?: GateRunner,
|
|
3892
|
-
judge: (opts: GateOptions, runner?: GateRunner) => Promise<GateResult> = judgeGate
|
|
4286
|
+
judge: (opts: GateOptions, runner?: GateRunner, imageProbe?: typeof preflightGateImageDigest) => Promise<GateResult> = judgeGate,
|
|
4287
|
+
imageProbe: typeof preflightGateImageDigest = preflightGateImageDigest): Promise<number> {
|
|
3893
4288
|
const start = Date.now();
|
|
3894
4289
|
let result: GateResult;
|
|
3895
4290
|
let version = '';
|
|
4291
|
+
let graphRunId: string | null = null;
|
|
4292
|
+
let autohealStoreRoot = '';
|
|
3896
4293
|
let placement: { executor: string | null; host: string | null } | undefined;
|
|
3897
4294
|
try {
|
|
3898
4295
|
const parsed = parseOptions(argv, env);
|
|
@@ -3907,12 +4304,14 @@ export async function runGateNode(argv: string[], env: NodeJS.ProcessEnv, runner
|
|
|
3907
4304
|
debug.log('release-loop.gate', 'executor', { version: opts.version, ...placement, requested: opts.executor ?? null });
|
|
3908
4305
|
}
|
|
3909
4306
|
if (opts === 'help') {
|
|
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.');
|
|
4307
|
+
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] [--full] [--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.\n--full disables receipt and partial reuse and runs the complete test inventory.');
|
|
3911
4308
|
return 0;
|
|
3912
4309
|
}
|
|
3913
4310
|
version = opts.version;
|
|
4311
|
+
autohealStoreRoot = opts.ledgerRoot ?? releaseLedgerRoot();
|
|
3914
4312
|
// The graph's existing gate-node execution path delegates verdict classification and persistence here.
|
|
3915
4313
|
const context = usesGraphContext(argv, env) ? readGraphContext(env) : undefined;
|
|
4314
|
+
graphRunId = context?.runId ?? null;
|
|
3916
4315
|
// Let the gate's existing SSH host validation report an invalid host before any reservation is published.
|
|
3917
4316
|
const hosts = opts.remote && !/^(?:[\w.-]+@)?[\w.-]+$/.test(opts.remote) ? [] : reservedGateHosts(opts);
|
|
3918
4317
|
const reservationRoot = context ? gateHostReservationRoot() : undefined;
|
|
@@ -3920,12 +4319,40 @@ export async function runGateNode(argv: string[], env: NodeJS.ProcessEnv, runner
|
|
|
3920
4319
|
throw new Error('release gate host reservation requires graph runId and pinned state root');
|
|
3921
4320
|
const releaseHosts = context && hosts.length
|
|
3922
4321
|
? reserveReleaseGateHosts(reservationRoot!, context.runId!, hosts) : () => undefined;
|
|
3923
|
-
try { result = await judge(context?.runId ? { ...opts, graphRunId: context.runId } : opts, runner); }
|
|
4322
|
+
try { result = await judge(context?.runId ? { ...opts, graphRunId: context.runId } : opts, runner, imageProbe); }
|
|
3924
4323
|
finally { releaseHosts(); }
|
|
3925
4324
|
} catch (error) {
|
|
3926
4325
|
result = { outcome: 'error', commit: '', introduced: [], preexisting: 0, fixed: 0, knownEnv: 0, knownEnvCleared: [], stalledEnv: [], rcNonzeroNoFailures: [], durationMs: Date.now() - start, error: String(error) };
|
|
3927
4326
|
placement ??= requestedGateExecutor(argv, env);
|
|
3928
4327
|
}
|
|
4328
|
+
if (versionPattern.test(version) && result.stalledShards?.length) {
|
|
4329
|
+
result.autoheals = result.stalledShards.map((shard, index) => {
|
|
4330
|
+
const shardKey = `pod-${shard.shard}`;
|
|
4331
|
+
const signature = createHash('sha256').update(JSON.stringify({ shard: shard.shard,
|
|
4332
|
+
files: [...shard.files].sort(), reason: shard.reason })).digest('hex');
|
|
4333
|
+
const cause = gateAutohealCause({ outcome: result.outcome, introduced: result.introduced, durationMs: result.durationMs,
|
|
4334
|
+
error: result.error, stalledShards: [shard], interferenceErrors: result.interferenceErrors, loadFlaky: result.loadFlaky }) ?? 'environment';
|
|
4335
|
+
const decision = decideAndRecordGateAutoheal({ cause, signature, version, runId: graphRunId ?? 'direct', shardKey,
|
|
4336
|
+
shardKeys: [shardKey], shardElapsedMs: result.durationMs,
|
|
4337
|
+
overallElapsedMs: index === 0 ? result.durationMs : 0,
|
|
4338
|
+
storeRoot: autohealStoreRoot || releaseLedgerRoot() });
|
|
4339
|
+
debug.log('release-loop.gate-autoheal', 'decision', { runId: graphRunId, version, ...decision });
|
|
4340
|
+
return decision;
|
|
4341
|
+
});
|
|
4342
|
+
result.autoheal = result.autoheals.find((decision) => decision.action === 'hitl') ?? result.autoheals[0];
|
|
4343
|
+
} else if (versionPattern.test(version)) {
|
|
4344
|
+
// Some failures (for example an unreachable gate host or a measured regression) have no
|
|
4345
|
+
// stalled shard. Keep them in the same durable policy ledger instead of silently skipping autoheal.
|
|
4346
|
+
const cause = gateAutohealCause(result);
|
|
4347
|
+
if (cause) {
|
|
4348
|
+
const signature = createHash('sha256').update(JSON.stringify({ cause,
|
|
4349
|
+
error: result.error ?? null, introduced: [...(result.introduced ?? [])].sort() })).digest('hex');
|
|
4350
|
+
result.autoheal = decideAndRecordGateAutoheal({ cause, signature, version, runId: graphRunId ?? 'direct',
|
|
4351
|
+
shardKey: 'gate', shardKeys: ['gate'], shardElapsedMs: result.durationMs, overallElapsedMs: result.durationMs,
|
|
4352
|
+
storeRoot: autohealStoreRoot || releaseLedgerRoot() });
|
|
4353
|
+
debug.log('release-loop.gate-autoheal', 'decision', { runId: graphRunId, version, ...result.autoheal });
|
|
4354
|
+
}
|
|
4355
|
+
}
|
|
3929
4356
|
if (result.error) console.error(result.error);
|
|
3930
4357
|
debug.log('release-loop.gate', 'result', { version, outcome: result.outcome, ...(placement ?? {}) });
|
|
3931
4358
|
emitNodeResult({ ...graphGateResult(result, !!env.ELANOUS_GRAPH_CONTEXT), ...(placement ?? {}) });
|