elanous 0.2.41 → 0.2.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/{GiOLWmt4MqDZePubBjA4Q → Om5xS9jpjSXJCeTNGYEi4}/_buildManifest.js +1 -1
- package/apps/pwa/out/_next/static/chunks/{2fe2c302.41b517f88d6fe502.js → 0966f8a1.745fcd44f64a87ea.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{385e4574.e9886ab32759eda3.js → 0ad6802e.ae7afb63fdcb4491.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/114.2b90424d1a3ce84c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1151-5c2bb8f002ca2dd6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1200.6a37c202f9f17e9a.js +226 -0
- package/apps/pwa/out/_next/static/chunks/1261-a616d5728e02e624.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1313-19f737e0222c4444.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1357.3bbef3976c37f94a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1362-20adddfaf662b0fa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6874.d5cf7eb8edb2cabe.js → 1406.a1dc2ab6f8449374.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/1737-9491fc6ab29b9ec6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1803-4f91a8e7dcaf042c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5744.b9415e18ad623100.js → 1961.a81b12872c3e93a9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1970cf59.2d4d8ce5d6c81853.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1971-8890f3962ba7ee0a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2073-ba274e00e4f9efb4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5471.1e611302ed3a7c53.js → 2144.25b292396712196d.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/{3230-49298c3b73ef2268.js → 2268-53861f339e0b65a2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{9834.63ab2bbc3153172e.js → 2312.0c8b61e22d3136d7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{1800.f7ff51dc4978a958.js → 2573.387a707f6f9d720c.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/273.f156eee28f11af77.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2873.d2140e5a82546401.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9048.212083de42d25be3.js → 3072.01338851ca21a6f7.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/3091.a5a4143dea33d197.js +90 -0
- package/apps/pwa/out/_next/static/chunks/3095.dc44586a5f83b86d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2263.e09b6edea33bad2d.js → 3113.d7472bbc27d580c7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3228-9650b6447b6fa288.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3402.44b565b314f2263c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{addc2b32.8c4ff8f0aefe987d.js → 3557ac4a.c8931684475018ad.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/360.ae9f9f4c33c395e8.js +79 -0
- package/apps/pwa/out/_next/static/chunks/3638.80412ea63aae8f3f.js +286 -0
- package/apps/pwa/out/_next/static/chunks/{a0a01613.6d1cca322238d7fe.js → 363bfab3.9623b9f89363541f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3694.e0099884345f0198.js +64 -0
- package/apps/pwa/out/_next/static/chunks/3744.54f87db52acfd1ab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3903.3ef4656c67572764.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3935.16701e33f8982dc9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3955.6922286237f2ac2b.js +24 -0
- package/apps/pwa/out/_next/static/chunks/3969.272ee7f9fd6c62e5.js +34 -0
- package/apps/pwa/out/_next/static/chunks/4199.ce86b281fd0a60b7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4311-6dd3144002cc0c2d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4426-2b230fad00c88930.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{530.d26346e18089640a.js → 4445.7990c76b3751a918.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4447-14c5161fa59166c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4856-b0b8075bba8c53f4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4870-de370d65e73c0bb5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4885-2c989165c4b29fad.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4894.fec2ca9e99caefa2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5093.8f04b7f8cb899a95.js +94 -0
- package/apps/pwa/out/_next/static/chunks/5122-2bb177cebf89e19a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5154.5f56dc3902e3d275.js +36 -0
- package/apps/pwa/out/_next/static/chunks/529.64ff7257180b2ac9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5394.5dabeb0c62293a28.js +93 -0
- package/apps/pwa/out/_next/static/chunks/5461.08bf2404ff38ec85.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5474-22e08a85583209b2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1219.7e17964e0e7cd8e5.js → 5564.9e72532188f57586.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/5567-265df39ed8fe9dcb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5570-a94ae19811cb2046.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5574-b47c9ef2dceff1fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5628.6ddb2d5ffba4ff4c.js +309 -0
- package/apps/pwa/out/_next/static/chunks/5730.43d7ccc09988e646.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1178.1aa9cad0e7695427.js → 5789.2da35c258292e1e5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{4133.2056bb4d78f621af.js → 5824.da9527b15a3221e5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5866.366966af47fdde5d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{4527.a69f2894d1bc9f4a.js → 5930.8b4df3b46c32b05a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5963-c4d8469c52a0828b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6001.ec4049ceb3c43418.js +27 -0
- package/apps/pwa/out/_next/static/chunks/6120-471f03db1e299c0c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6175-41479e643aca1878.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6291.a2de4f9787637075.js +104 -0
- package/apps/pwa/out/_next/static/chunks/6400-6699907c3537436a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6428.1f4327d6ae5fdf9a.js +287 -0
- package/apps/pwa/out/_next/static/chunks/644.02cb026fa44bcd0a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6466-6c87d60d3dd174fb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9507.7026657a2f47eb64.js → 6540.c7bef57ad8f90a87.js} +16 -16
- package/apps/pwa/out/_next/static/chunks/6773.d4264665ab31bec0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6822.3c789f1a8443e774.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5574.f35accf4f132b29d.js → 6944.309f4e3ea54d1537.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7261-8ff69d3b2ee671fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7266.084c6bbc70035140.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7271.5c0100e7320aa856.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7429-bf5731358c8f2f68.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7464.01f7a0ae1215fbe7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7479.0fcad5f37eb64f0c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7483.427a2fe2049e388d.js +148 -0
- package/apps/pwa/out/_next/static/chunks/7502-be74718178e13e56.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7530.0889722b19572065.js +24 -0
- package/apps/pwa/out/_next/static/chunks/7645.5b2ee3ad45c55ea3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7788.7d1c79f1a8e18946.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7900.096f455c7c1efc53.js +77 -0
- package/apps/pwa/out/_next/static/chunks/7910-6f139bcafd238b9f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7966.f71c8958d1637548.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5947-135e26537d80a6b9.js → 8062-7cd0bcc7f01455ba.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/8073.f6cf2f2c71aafbd0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8201.190b94088980f430.js +74 -0
- package/apps/pwa/out/_next/static/chunks/8205.282ca4bb62d54a0a.js +62 -0
- package/apps/pwa/out/_next/static/chunks/8313-dfa1021bb58a46d6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/837-81336ebaca8198a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8447.7963352ef5f71b76.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8574-868e8129525ef6d9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8588.d1c6a2903727ce87.js +149 -0
- package/apps/pwa/out/_next/static/chunks/8637.90f92d5c8db4732c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2743.5d3f76948be32b34.js → 868.6e953be05bcf6dca.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/8706-e1fe0309343a2273.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8749-06384cdfc4520add.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5964-7e21164a8f2cf0ce.js → 8861-cc1fa1c7b920fa8e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8967-7dfebf710c9dec9d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5159.115f1c2ca471386c.js → 8986.8e2e4efbfa655c59.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8997-3c564dbb3b58b055.js +1 -0
- package/apps/pwa/out/_next/static/chunks/923.2034d10ae91e9c20.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9312.bc1b89c5d4368033.js +1 -0
- package/apps/pwa/out/_next/static/chunks/943.39ae5e82f4f1893f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9469.0500480de55fb205.js +32 -0
- package/apps/pwa/out/_next/static/chunks/9770.5f75bc6088b16109.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9810-2543267050e58b82.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9870.8419702475254745.js +8 -0
- package/apps/pwa/out/_next/static/chunks/9941-376d1b74d51b01a4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9958.385434ab04241758.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{b2271e54.c1de3347976e90b0.js → aa57532f.510551854ff239b1.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-deea1450b17cb8dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-70985b904f8650ef.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-a2e15e33e56a4353.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-fc72ce2db12d6ca9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-be03e41f9436ebd9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-35a35999382da3f2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-3ff5b1e6ca88b848.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-a65101300a0ee5c1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-3aca5b6d67dba511.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-45b8ab0a71e61314.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-53046e282d4cda8e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-17809e60e8354c17.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-fd2442d347bf5898.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-e9d720d6fc082e21.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-6b8b4267bab0a677.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-deea1450b17cb8dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-8bcf6233ac7c17af.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-7cc25591cfc2126f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-053e871f35c9beba.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-eff819f0c2d5e084.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-4d7e9d8db5274a87.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-2b2d8f5aa5f69ad4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-0b8f92c5062e68b1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1bd9d0fdac2ced3d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-9ee28f7df07428ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-44b7a1ff468c6648.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-cbc6523711dc1b65.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-fdad47899c2695be.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-40f9e19149024298.js → page-79a8b5fee4c20871.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-03e8ac9606aeaffb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-4cd55c55ec287e2f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-db17b4305404161e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-30041d848350fd02.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-c540da022dbf86b0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-9399efc538da057f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-a142dfb0331a05f5.js → page-ca2d09128c585ca4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-0e2635aecd343038.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-ef7f4399b6b7726e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-deea1450b17cb8dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-caf530535b875468.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-eaa20c1543a5fd8e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-c541eb1b75120f61.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-ab5e213eda9479f4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-0a815f34367b6db6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-e858ba0ed9393b68.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-1638ee9dd6e0582f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-4cf5a399109391f2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b4396321238798ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-cb72bfd21006781a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-6135d0242afbc434.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-a1dccf907eddb856.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{53aeb4ee-51c92f8d683f5220.js → c7945435-b44d4dbe90ba0dcb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{2fdf2931.b431eee4fb468bc3.js → cb0f8df8.09a4a6be97e3fc5f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{c0c1a699.0ba79f97005965d4.js → d96660fb.cbc21a2a88539dbb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/ea514caa.05a24f78d9dffe5f.js +122 -0
- package/apps/pwa/out/_next/static/chunks/{83184dcd-0ee6247ff41eb3f2.js → fb7d20d5-6892502b6ee39ca6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/framework-e8a911e19b1be41f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-7022f418fabba021.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-e5f15df755756a1e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-4525f91e36940c9b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-140f0e519c085b0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-8f22ef63dcb865b1.js +1 -0
- package/apps/pwa/out/_next/static/css/{b9d8151e48998417.css → 62c858ad53854f47.css} +1 -1
- package/apps/pwa/out/_next/static/css/d1f8bb401740a549.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +23 -23
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +23 -23
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +23 -23
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +22 -22
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +22 -22
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +23 -23
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +22 -22
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +23 -23
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +23 -23
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +22 -22
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +23 -23
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +24 -25
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +23 -23
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +23 -23
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +22 -22
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +23 -23
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +23 -23
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +24 -25
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +24 -25
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +22 -22
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +22 -22
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +22 -22
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +23 -23
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +23 -23
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +22 -22
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +22 -22
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +22 -22
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +22 -22
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +23 -23
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +23 -23
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +22 -22
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +23 -23
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +23 -23
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +23 -23
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +23 -23
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +23 -23
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +22 -22
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +23 -23
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +23 -23
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +23 -23
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +23 -23
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +23 -23
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +23 -23
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +23 -23
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +23 -23
- package/catalog/external-commands.yaml +5 -3
- package/catalog/meta/catalogVersion.yaml +2 -1
- package/catalog/signing/catalog-signing-public.pem +3 -0
- package/docs/goal-context/04-mock-scenarios.md +13 -0
- package/graphs/catalog/catalog.yaml +1 -1
- package/graphs/heal/heal-loop.yaml +1 -0
- package/graphs/intake/intake-daily.yaml +16 -0
- package/graphs/intake/news-sources.yaml +1 -0
- package/graphs/intake/recipes.yaml +7 -0
- package/graphs/overlays/examples/research-runtime-empty-terminal.yaml +28 -0
- package/graphs/overlays/stop-heal-ladder.yaml +113 -0
- package/graphs/profiles/local.yaml +5 -0
- package/graphs/profiles/pod.yaml +4 -0
- package/graphs/units/verbs/recipes.yaml +11 -11
- package/graphs/work/research-report.yaml +10 -2
- package/package.json +6 -2
- package/plugins/demo-absorption-briefing/plugin.json +18 -0
- package/plugins/demo-absorption-briefing/skills/demo-absorption-briefing/SKILL.md +8 -0
- package/plugins/demo-browser-hand/plugin.json +13 -0
- package/plugins/demo-browser-hand/skills/demo-browser-hand/SKILL.md +8 -0
- package/plugins/demo-linear-secretary/plugin.json +15 -0
- package/plugins/demo-linear-secretary/skills/demo-linear-secretary/SKILL.md +8 -0
- package/plugins/demo-plugin-wizard/plugin.json +11 -0
- package/plugins/demo-plugin-wizard/skills/demo-plugin-wizard/SKILL.md +8 -0
- package/plugins/demo-research/plugin.json +16 -0
- package/plugins/demo-research/skills/demo-research/SKILL.md +8 -0
- package/plugins/demo-video-briefing/plugin.json +15 -0
- package/plugins/demo-video-briefing/skills/demo-video-briefing/SKILL.md +8 -0
- package/scripts/build-signed-catalog.ts +48 -0
- package/scripts/ci-typecheck-main-delta.sh +88 -0
- package/scripts/codex-fallback-live-probe.ts +1 -1
- package/scripts/demo/demo1104-parallel-scenes.ts +301 -0
- package/scripts/demo/demo1104-stage.config.ts +21 -0
- package/scripts/demo/demo1104-stage.ts +316 -0
- package/scripts/goal-method-audit.ts +17 -1
- package/scripts/graph-units/land-enqueue.ts +90 -0
- package/scripts/heal/run-role.ts +99 -5
- package/scripts/hooks/codex-notify.sh +32 -0
- package/scripts/install.sh +10 -38
- package/scripts/land-global-checks.ts +6 -1
- package/scripts/landing-heal/land-plan.ts +57 -0
- 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/release-loop/auto-approve-node.ts +1 -1
- package/scripts/release-loop/gate-autoheal-sim-evidence.ts +60 -0
- package/scripts/release-loop/gate-baseline-receipt.ts +47 -9
- package/scripts/release-loop/gate-image-pin.ts +81 -0
- package/scripts/release-loop/gate-method.ts +139 -0
- package/scripts/release-loop/gate-node-sim.ts +207 -0
- package/scripts/release-loop/gate-node.ts +463 -64
- 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-traffic-mode.ts +151 -3
- package/scripts/release-loop/gate-train-receipts.ts +122 -0
- package/scripts/release-loop/node-verdict.ts +38 -25
- package/scripts/release-loop/preflight.ts +362 -8
- package/scripts/release-loop/resume-release.ts +83 -8
- package/scripts/release-loop/unattended-release.ts +20 -9
- package/scripts/rhythm/daily.ts +36 -4
- package/scripts/rhythm/linear-progress-sync.ts +298 -0
- package/scripts/ux-sim.ts +18 -1
- package/scripts/video-pipeline.ts +9 -1
- package/scripts/warm-pool-provider-config.ts +286 -0
- package/skills/omni-crawl/SKILL.md +5 -2
- package/skills/omni-crawl/scripts/main.ts +84 -23
- package/src/acp/agent-manager.ts +1 -0
- package/src/acp/codex-app-server-agent.ts +33 -0
- package/src/acp/codex-app-server-client.ts +153 -16
- package/src/acp/core-turn-bridge.ts +27 -9
- package/src/acp/event-router.ts +20 -1
- package/src/acp/server.ts +6 -0
- 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/browser-roundtrip-coordinator.ts +85 -0
- package/src/agent-mission/claude-headless.ts +4 -1
- package/src/agent-mission/codex-exec.ts +105 -0
- package/src/agent-mission/driver.ts +192 -11
- package/src/agent-mission/grok-auth-status.ts +47 -0
- package/src/agent-mission/hand-hooks.ts +101 -0
- package/src/agent-mission/mission-cli.ts +63 -4
- package/src/agent-mission/mission-hand-reply.ts +118 -0
- package/src/agent-mission/mission-line.ts +180 -0
- package/src/agent-mission/mission-out.ts +833 -0
- package/src/agent-mission/tool-install.ts +200 -3
- package/src/agent-substrate/context-strategy.ts +4 -4
- package/src/agent-substrate/self-review-cli.ts +178 -13
- package/src/agents/general-purpose.md +1 -1
- package/src/auto-research/research-turn.ts +211 -53
- package/src/autopilot/pr-manager.ts +69 -10
- package/src/autopilot/pty-control-loop.ts +2 -0
- package/src/boot/daemon-tools/self-implement.ts +3 -1
- package/src/boot/daemon-tools/web-search.ts +1 -2
- package/src/briefing/brief-items.ts +55 -11
- package/src/capture/tui-self-report.ts +22 -6
- package/src/chat/index.ts +1 -1
- package/src/cli/agent-cli.ts +2 -1
- package/src/cli/catalog-cli.ts +31 -0
- package/src/cli/connector-cli.ts +16 -0
- package/src/cli/doctor-fix.ts +2 -2
- 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/intake-source-cli.ts +44 -8
- package/src/cli/msg-cli.ts +32 -3
- package/src/cli/pr-cli.ts +21 -1
- package/src/cli/pr-land-heavy-remote.ts +75 -5
- package/src/cli/pr-land-overlap-evidence.ts +113 -2
- package/src/cli/pr-land-pod-shadow-job.ts +202 -4
- package/src/cli/pr-land-pod-shadow.ts +9 -2
- package/src/cli/release-checklist-read-cli.ts +57 -19
- package/src/cli/release-cli.ts +23 -3
- package/src/cli/research-cli.ts +13 -5
- package/src/cli/setup-cli.ts +166 -6
- package/src/cli/setup-keys.ts +187 -0
- package/src/cli/task-plugin-wizard-host.ts +114 -0
- package/src/cli/tasks-cli.ts +61 -9
- package/src/cli/tui-test-cli.ts +186 -0
- package/src/compact/auto.ts +8 -1
- package/src/config.ts +4 -2
- package/src/connectors/linear-daily-progress.ts +228 -0
- package/src/connectors/linear-projector.ts +34 -12
- package/src/connectors/linear.ts +151 -7
- package/src/control-plane/ledger.ts +52 -2
- package/src/control-plane/server.ts +16 -2
- package/src/core-turn/deep-turn-bloom.ts +67 -0
- package/src/core-turn/research-policy.ts +37 -0
- package/src/core-turn/run-core-turn.ts +606 -95
- package/src/core-turn/run-goal-loop.ts +18 -0
- package/src/core-turn/types.ts +4 -0
- package/src/dashboard/chat/acp-chat.ts +10 -0
- package/src/dashboard/classic-sidetab-mission-job.ts +161 -0
- package/src/dashboard/classic-sidetab-mission-pair.ts +81 -0
- package/src/dashboard/classic-sidetab-mission-trio.ts +219 -0
- package/src/dashboard/classic-sidetab-mission.ts +347 -0
- package/src/dashboard/index.ts +517 -18
- package/src/dashboard/input/agent-wake.ts +23 -1
- package/src/dashboard/input/chat-main-acp-dispatch.ts +23 -0
- package/src/dashboard/input/chat-main-plain-turn-runtime.ts +17 -0
- package/src/dashboard/mission-cli-consumer.ts +165 -0
- package/src/dashboard/plugin-wizard-runtime.ts +50 -0
- package/src/dashboard/plugin-wizard-tool.ts +55 -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/terminal-popup-runtime.ts +6 -3
- package/src/dashboard/turn-preamble.ts +53 -15
- package/src/dashboard/turn-prompt-runtime.ts +4 -2
- package/src/decisions/linear-progress-summary.ts +78 -0
- package/src/decisions/linear-progress-sync.ts +152 -0
- package/src/domains/outbound-alert.ts +49 -11
- package/src/domains/telegram-kind-route.ts +16 -9
- package/src/graph-runner/arg-allowance.ts +13 -0
- package/src/graph-runner/graph-cli.ts +23 -7
- package/src/graph-runner/graph-run-api.ts +3 -1
- package/src/graph-runner/graph-tick.ts +10 -2
- package/src/graph-runner/runner.ts +86 -18
- package/src/graph-wizard/mission-compose.ts +407 -12
- package/src/grok/agent-search.ts +14 -4
- package/src/grok/credential.ts +28 -11
- package/src/harness/dispatch-backpressure.ts +54 -1
- package/src/harness/dispatch-shadow-compare.ts +74 -0
- package/src/harness/harness-cli-command.ts +11 -1
- package/src/harness/harness-pod-dispatch.ts +81 -15
- package/src/harness/harness-stop.ts +155 -11
- package/src/harness/skill-exec.ts +21 -9
- package/src/harness/skill-select.ts +1 -1
- package/src/hooks/error-report-cloud.ts +101 -0
- package/src/hooks/receiver.ts +29 -2
- package/src/index.ts +159 -13
- package/src/instance/spawn-elanous.ts +6 -4
- package/src/intake-plane/actions.ts +354 -0
- package/src/intake-plane/adapters/telegram-ambient.ts +34 -18
- package/src/intake-plane/intake-sources.ts +78 -8
- package/src/intake-plane/items.ts +14 -4
- package/src/intake-plane/linear-mission-sync.ts +80 -0
- package/src/intake-plane/linear-secretary-runtime.ts +178 -0
- package/src/intake-plane/linear-wish.ts +227 -1
- package/src/life/life-cli.ts +168 -0
- package/src/life/life-kinds.ts +160 -0
- package/src/live-supply/budget-consumer.ts +78 -0
- package/src/live-supply/read.ts +3 -0
- package/src/live-supply/snapshot.ts +45 -0
- package/src/llm/provider-credentials.ts +8 -2
- package/src/llm.ts +168 -9
- package/src/loops/orchestrator/coordinator-shadow.ts +226 -0
- package/src/loops/orchestrator/tick.ts +13 -0
- package/src/maturity/feature-maturity.ts +4 -4
- package/src/msg/msg-sse-server.ts +3 -3
- package/src/msg/msg-store.ts +177 -19
- package/src/native-tool-catalog.ts +38 -4
- package/src/nexus/api/harness-traversal.ts +11 -0
- package/src/nexus/api/http-server.ts +37 -1
- package/src/nexus/api/mcp-public-allowlist.ts +2 -0
- package/src/nexus/index.ts +7 -2
- package/src/nexus/outbound/format.ts +9 -0
- package/src/nexus/outbound/router.ts +98 -13
- package/src/nexus/setup-status.ts +9 -0
- package/src/nexus/state/state.ts +3 -0
- package/src/oauth/codex-account-store.ts +20 -7
- package/src/oauth/codex-owner-mirror.ts +44 -0
- package/src/oauth/codex.ts +17 -2
- package/src/oauth/fallback-chain.ts +47 -17
- package/src/onboarding/first-run-doctor.ts +8 -1
- package/src/onboarding/first-run.ts +85 -26
- package/src/onboarding/key-setup.ts +103 -0
- package/src/onboarding/menu-answer.ts +13 -0
- package/src/onboarding/secret-prompt.ts +37 -0
- package/src/plugins/install/plugin-install.ts +59 -0
- package/src/pr-tool/census.ts +133 -2
- package/src/pty-shell/pty-control-ipc.ts +4 -1
- package/src/pty-shell/tui-run-state.ts +188 -2
- package/src/registry/catalog-server.ts +232 -0
- package/src/registry/loader.ts +45 -1
- package/src/registry/types.ts +1 -2
- package/src/release-loop/checklist-target-path-hint.ts +13 -0
- package/src/release-loop/checklist.ts +9 -1
- package/src/repl/index.ts +43 -2
- package/src/research/axis-fanout.ts +136 -16
- package/src/research/bloom-exemplars.ts +3 -1
- 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 +72 -1
- package/src/self-dev/decompose.ts +119 -5
- package/src/self-dev/dev-cli.ts +4 -1
- package/src/self-dev/dev-pipeline.ts +5 -0
- package/src/self-dev/draft-residue.ts +64 -5
- package/src/self-dev/graph-front-nodes.ts +2 -1
- package/src/self-dev/graph-journey-route.ts +76 -21
- package/src/self-dev/launch-preflight-text.ts +5 -6
- package/src/self-dev/launch-preflight.ts +58 -0
- package/src/self-dev/orchestrate.ts +13 -3
- package/src/self-dev/review-provider-fallback.ts +9 -2
- package/src/self-dev/run-supervisor.ts +42 -4
- package/src/self-dev/run-types.ts +4 -0
- package/src/self-dev/shard-aggregate.ts +4 -0
- package/src/self-dev/shard-run.ts +36 -15
- package/src/self-implement/abandoned-classification.ts +12 -3
- package/src/self-implement/budget-gate.ts +7 -0
- package/src/self-implement/child-llm-preference.ts +10 -1
- package/src/self-implement/doctor-visit.ts +118 -8
- package/src/self-implement/elanous-tui-spawn.ts +3 -0
- package/src/self-implement/fixtures/tdd-green-missing-passing-child-transcript.txt +2 -0
- package/src/self-implement/gate-artifacts.ts +2 -1
- package/src/self-implement/goal-author-cli.ts +4 -2
- package/src/self-implement/goal-author.ts +684 -7
- package/src/self-implement/graph-overlay-yaml.ts +37 -4
- package/src/self-implement/graph-overlay.ts +3 -1
- package/src/self-implement/graph-profile-yaml.ts +107 -0
- package/src/self-implement/graph-yaml.ts +26 -3
- 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 +27 -4
- package/src/self-implement/merge-train.ts +5 -0
- package/src/self-implement/off-diff-evidence.ts +249 -5
- package/src/self-implement/orchestrator.ts +365 -25
- 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 +122 -0
- package/src/self-implement/run-ledger.ts +162 -11
- package/src/self-implement/run-stop.ts +188 -4
- package/src/self-implement/runtime-overlay-run-steps.ts +95 -0
- package/src/self-implement/stop-autoheal.ts +13 -0
- package/src/self-implement/tdd-evidence-tags.ts +9 -0
- package/src/self-implement/train-gate-receipt.ts +166 -0
- package/src/self-implement/train-gate-remote.ts +62 -18
- package/src/session/chat.ts +40 -4
- package/src/session/session-fanout.ts +2 -0
- package/src/session/streaming/acp-stream-sink.ts +2 -1
- package/src/session/streaming/chunk-producer.ts +6 -0
- package/src/session/streaming/stream-compositor.ts +3 -2
- package/src/session-runtime/index.ts +135 -5
- package/src/session-runtime/retry-policy.ts +1 -1
- package/src/session-runtime/tier-flip.ts +3 -3
- package/src/setup/llm-env-detect.ts +3 -0
- package/src/setup/service-key-catalog.ts +46 -0
- package/src/setup/service-keys.ts +241 -0
- package/src/sidetab-core/model.ts +19 -5
- package/src/sidetab-core/visible-agent-mission.ts +262 -0
- package/src/skills/name-match.ts +20 -0
- package/src/skills/tools/agent.ts +53 -6
- package/src/skills/tools/omni-search.ts +173 -13
- package/src/task-agent/parent-host.ts +10 -0
- package/src/task-agent/pod-distributor-entry.ts +3 -0
- package/src/task-agent/repair-outcomes.ts +65 -0
- package/src/task-agent/task-hand.ts +47 -7
- package/src/task-orchestrator/dispatcher.ts +14 -1
- package/src/task-orchestrator/linear-capture-events.ts +55 -0
- package/src/task-orchestrator/store.ts +16 -5
- package/src/task-orchestrator/surfaces/pod-artifact-return.ts +4 -0
- package/src/task-orchestrator/surfaces/pod-distributor.ts +48 -0
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +92 -1
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +38 -1
- package/src/task-orchestrator/surfaces/pod-ledger-events.ts +113 -14
- 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 +178 -16
- package/src/task-orchestrator/surfaces/pod-source-spec.ts +3 -3
- package/src/task-orchestrator/surfaces/pod-warm-switch.ts +2 -0
- package/src/task-orchestrator/surfaces/pod-warm-worker-provider.ts +237 -41
- package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +80 -3
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +337 -51
- package/src/task-orchestrator/surfaces/self-implement.ts +27 -3
- package/src/task-orchestrator/surfaces/warm-reuse-evidence.ts +169 -0
- package/src/task-orchestrator/types.ts +14 -0
- package/src/telegram.ts +14 -16
- package/src/terminal/coding-agent.ts +14 -5
- package/src/terminal/session-registry.ts +1 -1
- package/src/tokens.ts +35 -2
- package/src/tool-hints/gate.ts +1 -0
- package/src/tool-runtime/agent-runtime.ts +2 -0
- package/src/tool-runtime/browser-hand-runtime.ts +381 -0
- package/src/tool-runtime/browser-runtime.ts +2 -1
- package/src/tool-runtime/elanous-linear-issue-runtime.ts +76 -0
- package/src/tool-runtime/elanous-linear-secretary-runtime.ts +57 -0
- package/src/tool-runtime/elanous-skills-list-runtime.ts +5 -1
- package/src/tool-runtime/graph-mcp-runtimes.ts +240 -0
- package/src/tool-runtime/index.ts +6 -0
- package/src/tool-runtime/skill-exec-runtime.ts +33 -13
- package/src/tool-runtime/types.ts +4 -0
- package/src/tui/entry.ts +38 -5
- package/src/tui/sidetab/attach.ts +177 -1
- package/src/tui/sidetab/herdr-portal.ts +225 -0
- package/src/tui-client/local-chat-turn.ts +26 -13
- package/src/tui.ts +22 -0
- package/src/user-config.ts +34 -9
- package/src/user-intent/types.ts +4 -3
- package/src/ux-sim/browser-hand.ts +165 -0
- package/src/ux-sim/draft-inventory-builder.ts +198 -0
- package/src/ux-sim/fake-browser-page.ts +81 -0
- package/src/ux-sim/fake-kubectl.ts +37 -3
- package/src/ux-sim/fake-linear-device.ts +31 -0
- package/src/ux-sim/fault-pool-cap.ts +11 -6
- package/src/ux-sim/graph-engine-sim.ts +47 -0
- package/src/ux-sim/linear-secretary.ts +216 -0
- package/src/ux-sim/mock-scenario-catalog.json +93 -0
- package/src/ux-sim/mock-scenario-catalog.ts +274 -0
- package/src/ux-sim/onboarding-first-run-device.ts +31 -21
- package/src/ux-sim/scenario.ts +185 -48
- package/src/ux-sim/service-key-setup.ts +50 -0
- package/src/ux-sim/term-slash-device.ts +74 -0
- package/src/ux-sim/turn-throw-sim.ts +31 -0
- package/src/version/packed-revision.json +1 -1
- package/src/voice/voice-tts-singleton.ts +18 -3
- package/src/web-search/exa.ts +77 -0
- package/src/web-search/grok.ts +5 -9
- package/src/web-search/index.ts +30 -9
- package/src/web-search/provider.ts +4 -1
- package/src/web-search/x-intent.ts +5 -0
- package/apps/pwa/out/_next/static/chunks/1115.434ae2ecb0ad986d.js +0 -149
- package/apps/pwa/out/_next/static/chunks/1121.667cec0bcdbb3fdb.js +0 -36
- package/apps/pwa/out/_next/static/chunks/1197-54da59d250a6e476.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1245.1cfd13b26dbd71d6.js +0 -287
- package/apps/pwa/out/_next/static/chunks/1494-f93046ff5c5a124d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1609.2b312797599b8e45.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1661.0dd330f3d89f73f3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/167-0c121e4b47e0f9da.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1700.cdc2bf8cc685e221.js +0 -90
- package/apps/pwa/out/_next/static/chunks/1703-78e46acad29f3cfc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1765.679b29625c8e88d4.js +0 -77
- package/apps/pwa/out/_next/static/chunks/1971-7efb628c471dc5a0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1999-8de7b7bb36801913.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2073.689dcb2680113d76.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2198-557d49184104d165.js +0 -1
- package/apps/pwa/out/_next/static/chunks/221-440ce67fa9f5c839.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2427-8ed8e98624cbd664.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2457-725c5827bc003795.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2491-2718b15a5ccb75a5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2527-5c636a43c7cc5dde.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2692-1027cb16fe4ccf49.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2773.0e80b9d325fb5635.js +0 -32
- package/apps/pwa/out/_next/static/chunks/2873-0f3cb56c8e3528ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3097.864e92bdaa2f4d15.js +0 -79
- package/apps/pwa/out/_next/static/chunks/3136-d6870ba63300a93e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3173-0289113d434e83b7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/325-dd9208a1a0ab7ce4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3283.528f430dd043fb4e.js +0 -74
- package/apps/pwa/out/_next/static/chunks/3295.cc8cb13b8f0dae1a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3688-86e3b88abf18cb3b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3804-7220467a5473a8df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3808.3e1e247ce7bdf889.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3927.b5473ad109da44c5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3934-98ea94832e194796.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4156.ba4de4a3664f04b4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4297-239ca82f293faab0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4320-dcc5a7734126f4c9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4434.94b4c552a2d91e69.js +0 -148
- package/apps/pwa/out/_next/static/chunks/4579.03e4092f73950965.js +0 -226
- package/apps/pwa/out/_next/static/chunks/4667.97e31aa38c95c475.js +0 -309
- package/apps/pwa/out/_next/static/chunks/4702.4a4adc1f16d7c93b.js +0 -64
- package/apps/pwa/out/_next/static/chunks/4815.481501c57b3568d9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/487-d16e698ef14662d9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4888-7a38bf522c9cc6fe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5034-30e429d6bb086881.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5040-35c494e60dadd97d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/50c6ab1e.81a96e419350909d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5154.4b6a836179356f30.js +0 -94
- package/apps/pwa/out/_next/static/chunks/5228-a8200df486278f2d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5379.97af106d09566f69.js +0 -8
- package/apps/pwa/out/_next/static/chunks/5479-b6372e762719f92f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5534-699c5cbcbc96c5d7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5701.9c4e8f250312eeee.js +0 -24
- package/apps/pwa/out/_next/static/chunks/5892.2526d962339662d8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5937.c2df9e3447e1f667.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5941.8210cc93c9202c0a.js +0 -286
- package/apps/pwa/out/_next/static/chunks/5975.4004f820ae3cecd5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6142-7ccd48795d7abd55.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6160.bf9177b8c1bfb243.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6398.71ba0690c8027555.js +0 -27
- package/apps/pwa/out/_next/static/chunks/6492.2bd5c10edc1a064e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6552.a3840aa7da35ad68.js +0 -62
- package/apps/pwa/out/_next/static/chunks/6736.1e36d3ac5fa5e41d.js +0 -24
- package/apps/pwa/out/_next/static/chunks/6900.3ccbe069bdd4556a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/71327eb4.add50b365f94bf3a.js +0 -122
- package/apps/pwa/out/_next/static/chunks/7136.deca3e3a2ddaa374.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7139-ee9f8ed2677a40b3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7191.fdc955f7d96064a4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7423-e0aa5d9e2962120e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7453.afc1ea7e05d56a8c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7503-5e6a3d7fe67e4a0c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7761-476fd7bf768abd34.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7975.eafb12097a0a9e62.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8050-9b364bdef8f71927.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8152.64e253c35cccfaae.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8190.620f0231b3924bcb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8448.45b6b1f1a99eeb35.js +0 -104
- package/apps/pwa/out/_next/static/chunks/8464-e24f10451b658aac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8477.e4c944bb3aa5d2f3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8710.ca385257d5ca422e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8830.627ccf294ec2465c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8880.64c445592f04acaf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8981.c706964c8ba27aff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9053.bd0fffaff8d3f836.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9161-c66e723d7ec9d682.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9185.1ecdf9b65c0bb684.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9356.fccd5f58086b4ff5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9446.dc0406109371d329.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9506.dcf25d467a42627e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9541.eb8e2f8480c7feca.js +0 -93
- package/apps/pwa/out/_next/static/chunks/9577.ed7582186efa5482.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9579.4a8169600fff2058.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9642-ff2e039eb62e3b92.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9659-8f2323348113ad80.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9724.4c5623af2db43153.js +0 -34
- package/apps/pwa/out/_next/static/chunks/9869.81a1e24ca682d5a0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-8d9d76ea322d855c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-ed2c4e59f9f1fe21.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-2dc99e03c5d76ca0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-b5a3a788330e920f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-14949db884bfa672.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-ea76c85b36388a97.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-01716d20af62d325.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-62baf6881a64b1e9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-f89ba590c2af8afa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-22f8b0f78d9637e5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-817f9d7e6820be80.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-ffb9bc0d2650249a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-f6d0e64873c0f629.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-8bfa0d4ba23a209d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-f5060462249e32c7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-8d9d76ea322d855c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-b2049f8b1ef79117.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-1446c5fa93907f11.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-b48a7a07c5204156.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-e974b8c4ca47614c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-8a919b869bd28594.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-d31b8de6bfcd72ed.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-64765f817909db29.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-d128c8569a36ee9b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-b2679d357e9b9270.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-6fabec98a9e7b269.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-048f30149dcf1349.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-9113181d6b5a2725.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-1e7a755a13227611.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-3c8f8b67a203cf07.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-18032f5437215062.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-da3b739192fd0ee3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-d332545571bd6fd1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-5237aa5b3f3b8ec5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-d16e56c3989c147e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-6b6593600869e2cd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-8d9d76ea322d855c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-29e8b8a8396b55cb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-b3414d856a1a7e2f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-c3affa52b7ce961b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-f21dddf9f95b6fab.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-f5a30a24745bf088.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-8c8569a09b4d4e34.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-2ce32722c0243483.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-4767b0d9c0a98c6c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-116ee1339c387a75.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-35e8c50ca4ef7ffc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-52e17113356bb891.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-7458ae05ed2ac1a0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-ad6c9dd524ad6ef8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-2ddd1f2b44d6aa59.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-e3d82bdc358d20c5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-71d5aa3896e1655f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-03ffc54e00438546.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-56bb89af5a611d0b.js +0 -1
- package/apps/pwa/out/_next/static/css/be9019f696217521.css +0 -1
- package/apps/pwa/out/_next/static/css/d0a48dc099ed86cf.css +0 -1
- /package/apps/pwa/out/_next/static/{GiOLWmt4MqDZePubBjA4Q → Om5xS9jpjSXJCeTNGYEi4}/_ssgManifest.js +0 -0
|
@@ -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,10 +49,10 @@ 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
|
-
import { runWithReleaseGateTraffic } from './gate-traffic-mode.js';
|
|
55
|
+
import { runWithReleaseGateTraffic, runWithReleasePwaTraffic } 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';
|
|
@@ -113,12 +117,28 @@ export interface GateTools {
|
|
|
113
117
|
podCommand?: (options: RunPodCommandOptions) => Promise<PodCommandResult>;
|
|
114
118
|
/** Kubernetes client (pool readiness · Pod lifecycle · Job log tail · failure evidence); absent = `defaultKubectl`. */
|
|
115
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;
|
|
116
122
|
/** SSH transport behind {@link GateRunner.command} on a remote host; absent = a real `ssh` child. */
|
|
117
123
|
ssh?: (host: string, script: string, limitMs?: number) => { rc: number; output: string; timedOut?: boolean };
|
|
118
124
|
/** SSH reachability probe of a multi-host roster; absent = `defaultRemoteRun`. */
|
|
119
125
|
remoteRun?: typeof defaultRemoteRun;
|
|
120
126
|
/** Pool admission a shard waits on (leases ⊕ a cluster reading); absent = the gate's own pool admission. */
|
|
121
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[];
|
|
122
142
|
}
|
|
123
143
|
export interface GateRunner {
|
|
124
144
|
command(cmd: string, args: string[], cwd: string, limitMs?: number): Promise<CommandResult>;
|
|
@@ -128,7 +148,7 @@ export interface GateRunner {
|
|
|
128
148
|
/** One checkout/install for a small independent file group; each file retains its own rc and log. */
|
|
129
149
|
recheckPodBatch?(files: readonly string[], commit: string, pod: NonNullable<GateOptions['pod']>, timeoutMs: number): Promise<Map<string, CommandResult>>;
|
|
130
150
|
/** `only` (GATE-PARTIAL) restricts the sweep to these test files; absent = every test file. */
|
|
131
|
-
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>;
|
|
132
152
|
add(tree: string, commit: string): Promise<void>;
|
|
133
153
|
remove(tree: string): Promise<void>;
|
|
134
154
|
snapshot(tree: string, commit: string): Promise<void>;
|
|
@@ -137,6 +157,7 @@ export interface GateRunner {
|
|
|
137
157
|
remoteMirror?: string;
|
|
138
158
|
/** GATE-FLOW-SIM — the clock this runner measures with (the injected fake clock in a scenario); absent = `Date.now`. */
|
|
139
159
|
now?(): number;
|
|
160
|
+
progressScheduler?: GateProgressScheduler;
|
|
140
161
|
}
|
|
141
162
|
|
|
142
163
|
/** One watchdog tick. Batch Kubernetes reads per host and trust only the current attempt's result marker. */
|
|
@@ -206,11 +227,17 @@ export function reconcileStaleGateShardBoard(input: {
|
|
|
206
227
|
}
|
|
207
228
|
export interface GateOptions {
|
|
208
229
|
commit: string;
|
|
230
|
+
/** Explicit pre-cut warm↔fresh comparison, persisted before the ordinary gate reads warmHistory. */
|
|
231
|
+
warmPrecutCanary?: { file: string };
|
|
232
|
+
/** RELEASE-POLICY-PACK — the gate Pod image is tied to this resolved cut commit. */
|
|
233
|
+
imagePin?: 'cut-commit';
|
|
209
234
|
/** The graph run that launched this gate; direct hard rehearsal instead pins source bytes explicitly. */
|
|
210
235
|
graphRunId?: string;
|
|
211
236
|
/** Only a direct hard rehearsal may pin graph bytes from its measured Git commit. */
|
|
212
237
|
rehearsalBaseline?: boolean;
|
|
213
238
|
forceFreeze?: boolean;
|
|
239
|
+
/** Force all release tests to run, bypassing every reusable partial plan. */
|
|
240
|
+
full?: boolean;
|
|
214
241
|
version: string;
|
|
215
242
|
baselineVersion?: string;
|
|
216
243
|
baselineCommit?: string;
|
|
@@ -227,6 +254,10 @@ export interface GateOptions {
|
|
|
227
254
|
recheckFileLimit?: number;
|
|
228
255
|
/** freshShards — GATE-SHARD-RESUME off: every shard starts a Pod even when this logDir already holds its result. */
|
|
229
256
|
pod?: { pool: string; shards?: number; shardTimeoutSeconds?: number; durationSource?: string; bunCache?: string; freshShards?: boolean;
|
|
257
|
+
imagePin?: 'cut-commit';
|
|
258
|
+
/** One immutable container image identity for this gate graph run and all its resumes. */
|
|
259
|
+
pinnedImageDigest?: string;
|
|
260
|
+
pinnedRunId?: string;
|
|
230
261
|
/** Remeasure one named shard, or all incomplete shards with auto. Complete siblings keep their receipts. */
|
|
231
262
|
rerouteShard?: string | 'auto';
|
|
232
263
|
/** Pool context for the rerouted shard; the planner otherwise distributes missing shards over pool contexts. */
|
|
@@ -346,7 +377,7 @@ export interface GateResult {
|
|
|
346
377
|
/** GATE-WARM-BOOT-SCAN — observation only: the cut sweep's test selection time (pod sweep · absent otherwise). */
|
|
347
378
|
selectionMs?: number;
|
|
348
379
|
error?: string;
|
|
349
|
-
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;
|
|
380
|
+
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;
|
|
350
381
|
/** Pool member that ran the final unmeasured attempt; enables a bounded cross-host tail remeasure. */
|
|
351
382
|
lastHost?: string;
|
|
352
383
|
/** Failure of the optional cross-host tail attempt; original stall fields remain authoritative. */
|
|
@@ -360,6 +391,7 @@ export interface GateResult {
|
|
|
360
391
|
partialFailures?: SweepFailures;
|
|
361
392
|
/** GATE-PARTIAL — this verdict re-ran only these files and carried the prior gate's results for the rest. */
|
|
362
393
|
partial?: { priorCommit: string; files: number };
|
|
394
|
+
trainReceiptReuse?: { carried: number; total: number; receipts: number; reasons: string[] };
|
|
363
395
|
/** A failure that passed an evidence-checked isolated re-run: warning (flaky under load), not an introduced regression. */
|
|
364
396
|
loadFlaky?: string[];
|
|
365
397
|
/** File-level unhandled errors that reproduced or could not be measured in isolation. They keep the gate red. */
|
|
@@ -382,29 +414,151 @@ const WARM_REPLAY_BUDGET = 3;
|
|
|
382
414
|
interface WarmVerdictSample { version: string; commit: string; baselineCommit: string; warmShadow: WarmShadowObservation[] }
|
|
383
415
|
const recordValue = (value: unknown): Record<string, unknown> | null =>
|
|
384
416
|
value !== null && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : null;
|
|
417
|
+
function validWarmShadowObservation(value: unknown): value is WarmShadowObservation {
|
|
418
|
+
const row = recordValue(value);
|
|
419
|
+
if (!row || !Array.isArray(row.files) || row.files.length !== 1 || typeof row.files[0] !== 'string'
|
|
420
|
+
|| !/^(?:[\w.-]+\/)*[\w.-]+\.test\.[cm]?[jt]sx?$/.test(row.files[0])
|
|
421
|
+
|| row.files[0].split('/').some((part) => part === '.' || part === '..')
|
|
422
|
+
|| typeof row.attemptId !== 'string' || !row.attemptId || typeof row.reason !== 'string'
|
|
423
|
+
|| !['observed', 'fallback', 'unmeasured'].includes(String(row.status))
|
|
424
|
+
|| !['yes', 'no', 'failed', 'unavailable'].includes(String(row.quarantined))) return false;
|
|
425
|
+
if (row.finalized !== undefined && typeof row.finalized !== 'boolean') return false;
|
|
426
|
+
if (row.cutSha !== undefined && typeof row.cutSha !== 'string') return false;
|
|
427
|
+
if (row.baselineSha !== undefined && typeof row.baselineSha !== 'string') return false;
|
|
428
|
+
if (row.workerKey !== undefined) {
|
|
429
|
+
const key = recordValue(row.workerKey);
|
|
430
|
+
if (!key || typeof key.context !== 'string' || !key.context || typeof key.imageDigest !== 'string' || !key.imageDigest
|
|
431
|
+
|| typeof key.lockHash !== 'string' || !key.lockHash || typeof key.architecture !== 'string' || !key.architecture) return false;
|
|
432
|
+
}
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
function validWarmVerdictSample(value: unknown): value is WarmVerdictSample {
|
|
436
|
+
const row = recordValue(value);
|
|
437
|
+
return !!row && typeof row.version === 'string' && typeof row.commit === 'string'
|
|
438
|
+
&& typeof row.baselineCommit === 'string' && Array.isArray(row.warmShadow)
|
|
439
|
+
&& row.warmShadow.every(validWarmShadowObservation);
|
|
440
|
+
}
|
|
385
441
|
/** The verdict is atomically replaced; carry a bounded prior-verdict window in that same SSOT. */
|
|
386
442
|
function readWarmVerdictHistory(path: string): WarmVerdictSample[] | undefined {
|
|
387
443
|
if (!existsSync(path)) return [];
|
|
388
444
|
try {
|
|
389
445
|
const current = recordValue(JSON.parse(readFileSync(path, 'utf8')));
|
|
390
446
|
if (!current || !Array.isArray(current.warmHistory)) return undefined;
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
447
|
+
if (current.warmHistory.some((entry) => !validWarmVerdictSample(entry))) return undefined;
|
|
448
|
+
const hasCurrentSample = ['version', 'commit', 'baselineCommit', 'warmShadow'].some((key) => key in current);
|
|
449
|
+
const history = [...current.warmHistory];
|
|
450
|
+
if (current.warmHistoryOnly !== true || hasCurrentSample) {
|
|
451
|
+
const currentSample = { version: current.version, commit: current.commit,
|
|
452
|
+
baselineCommit: current.baselineCommit, warmShadow: current.warmShadow };
|
|
453
|
+
if (!validWarmVerdictSample(currentSample)) return undefined;
|
|
454
|
+
if (current.warmHistoryOnly !== true) history.push(currentSample);
|
|
455
|
+
}
|
|
456
|
+
return history.slice(-WARM_VERDICT_HISTORY_LIMIT) as WarmVerdictSample[];
|
|
397
457
|
} catch { return undefined; }
|
|
398
458
|
}
|
|
459
|
+
|
|
460
|
+
/** Append one actual pre-cut replay observation to the gate verdict's bounded history, preserving its gate result. */
|
|
461
|
+
function recordWarmPrecutCanarySample(path: string, sample: WarmVerdictSample): void {
|
|
462
|
+
if (!sample.version || !/^[0-9a-f]{7,40}$/i.test(sample.commit) || !/^[0-9a-f]{7,40}$/i.test(sample.baselineCommit)
|
|
463
|
+
|| !Array.isArray(sample.warmShadow) || sample.warmShadow.length !== 1 || !sample.warmShadow.every(validWarmShadowObservation))
|
|
464
|
+
throw new Error('invalid pre-cut canary sample');
|
|
465
|
+
let current: Record<string, unknown> = {};
|
|
466
|
+
if (existsSync(path)) {
|
|
467
|
+
let decoded: unknown;
|
|
468
|
+
try { decoded = JSON.parse(readFileSync(path, 'utf8')); }
|
|
469
|
+
catch (error) { throw new Error(`gate verdict JSON is malformed: ${String(error)}`); }
|
|
470
|
+
const parsed = recordValue(decoded);
|
|
471
|
+
if (!parsed) throw new Error('gate verdict is not an object');
|
|
472
|
+
current = parsed;
|
|
473
|
+
if (current.warmHistory !== undefined && !Array.isArray(current.warmHistory)) throw new Error('gate verdict warmHistory is invalid');
|
|
474
|
+
}
|
|
475
|
+
const storedHistory = Array.isArray(current.warmHistory) ? [...current.warmHistory] : [];
|
|
476
|
+
if (storedHistory.some((entry) => !validWarmVerdictSample(entry))) throw new Error('gate verdict warmHistory contains an invalid row');
|
|
477
|
+
const history = storedHistory;
|
|
478
|
+
const hasCurrentSample = ['version', 'commit', 'baselineCommit', 'warmShadow'].some((key) => key in current);
|
|
479
|
+
if (current.warmHistoryOnly === true && hasCurrentSample) {
|
|
480
|
+
const previous = { version: current.version, commit: current.commit, baselineCommit: current.baselineCommit, warmShadow: current.warmShadow };
|
|
481
|
+
if (!validWarmVerdictSample(previous)) throw new Error('gate verdict current warm sample is invalid');
|
|
482
|
+
} else if (current.warmHistoryOnly !== true) {
|
|
483
|
+
const previous = { version: current.version, commit: current.commit, baselineCommit: current.baselineCommit, warmShadow: current.warmShadow };
|
|
484
|
+
if (!validWarmVerdictSample(previous)) throw new Error('gate verdict current warm sample is invalid');
|
|
485
|
+
history.push(previous);
|
|
486
|
+
}
|
|
487
|
+
history.push(sample);
|
|
488
|
+
const body = JSON.stringify({ ...current, warmHistory: history.slice(-WARM_VERDICT_HISTORY_LIMIT), warmHistoryOnly: true }, null, 2) + '\n';
|
|
489
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
490
|
+
const temp = join(dirname(path), `.gate-verdict-${process.pid}-${randomUUID()}.tmp`);
|
|
491
|
+
try { writeFileSync(temp, body, { mode: 0o600, flag: 'wx' }); chmodSync(temp, 0o600); renameSync(temp, path); }
|
|
492
|
+
finally { if (existsSync(temp)) rmSync(temp); }
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/** Run a bounded set of real fresh-Job ↔ warm-worker comparisons before the release gate. */
|
|
496
|
+
export async function runWarmPrecutCanary(input: { opts: GateOptions; runner: GateRunner; file: string;
|
|
497
|
+
config: unknown; historyPath: string; timeoutMs?: number }): Promise<{ samples: WarmShadowObservation[]; decision: ReturnType<typeof decideWarmReuse> }> {
|
|
498
|
+
const { opts, runner } = input;
|
|
499
|
+
if (!opts.pod || !runner.recheckPod || !opts.baselineCommit || !opts.pod.warmReplay || !opts.pod.warmFinalize || !opts.pod.warmQuarantine)
|
|
500
|
+
throw new Error('pre-cut canary requires a Pod pool, baseline commit, and warm worker hooks');
|
|
501
|
+
if (!/^(?:[\w.-]+\/)*[\w.-]+\.test\.[cm]?[jt]sx?$/.test(input.file)
|
|
502
|
+
|| input.file.split('/').some((part) => part === '.' || part === '..')) throw new Error('pre-cut canary requires one repository test file');
|
|
503
|
+
if (readWarmVerdictHistory(input.historyPath) === undefined)
|
|
504
|
+
throw new Error('gate verdict contains malformed warm history or current sample; refusing canary without writing');
|
|
505
|
+
const samples = 2;
|
|
506
|
+
if (samples > WARM_REPLAY_BUDGET) throw new Error(`pre-cut canary sample count exceeds replay budget (${WARM_REPLAY_BUDGET})`);
|
|
507
|
+
const lock = await runner.localCommand('git', ['show', `${opts.commit}:bun.lock`], opts.repo ?? process.cwd());
|
|
508
|
+
if (lock.rc !== 0 || !lock.output) throw new Error('pre-cut canary cut lock is unmeasured');
|
|
509
|
+
if (!/^[0-9a-f]{40}$/i.test(opts.baselineCommit)) throw new Error('pre-cut canary baseline commit is invalid');
|
|
510
|
+
const baseline = await runner.localCommand('git', ['cat-file', '-e', `${opts.baselineCommit}^{commit}`], opts.repo ?? process.cwd());
|
|
511
|
+
if (baseline.rc !== 0) throw new Error(`pre-cut canary baseline commit is absent: ${opts.baselineCommit.slice(0, 12)}`);
|
|
512
|
+
const trackedFile = await runner.localCommand('git', ['cat-file', '-e', `${opts.commit}:${input.file}`], opts.repo ?? process.cwd());
|
|
513
|
+
if (trackedFile.rc !== 0) throw new Error(`pre-cut canary test file is absent from cut ${opts.commit.slice(0, 12)}`);
|
|
514
|
+
const lockHash = createHash('sha256').update(lock.output).digest('hex');
|
|
515
|
+
const pod = { ...opts.pod, warmProducerEnabled: false, warmCutSha: opts.commit, warmBaselineSha: opts.baselineCommit,
|
|
516
|
+
warmEligible: true, warmReplayOwner: true, warmShadowScope: { requested: false, attempts: 0 } };
|
|
517
|
+
const observations: WarmShadowObservation[] = [];
|
|
518
|
+
const attemptIds = new Set<string>();
|
|
519
|
+
for (let index = 0; index < samples; index++) {
|
|
520
|
+
let observation: WarmShadowObservation;
|
|
521
|
+
let failure: Error | undefined;
|
|
522
|
+
try {
|
|
523
|
+
const result = await runner.recheckPod(input.file, opts.commit, pod, input.timeoutMs ?? GATE_ISOLATED_TIMEOUT_MS);
|
|
524
|
+
observation = result.warmShadow ?? { files: [input.file], attemptId: `precut-unmeasured-${index + 1}-${randomUUID()}`,
|
|
525
|
+
status: 'unmeasured', reason: result.rc === 0 ? 'warm-shadow-missing' : `fresh-job-rc-${result.rc}`, quarantined: 'no' };
|
|
526
|
+
if (result.rc !== 0) { observation = { ...observation, status: 'unmeasured', reason: `fresh-job-rc-${result.rc}` };
|
|
527
|
+
failure = new Error(`pre-cut fresh Job failed on attempt ${index + 1}: rc=${result.rc}`); }
|
|
528
|
+
else if (observation.status !== 'observed' || !observation.workerKey
|
|
529
|
+
|| observation.files.length !== 1 || observation.files[0] !== input.file
|
|
530
|
+
|| observation.cutSha !== opts.commit || observation.baselineSha !== opts.baselineCommit
|
|
531
|
+
|| observation.workerKey.lockHash !== lockHash || !['match', 'result'].includes(observation.reason)
|
|
532
|
+
|| attemptIds.has(observation.attemptId)
|
|
533
|
+
|| (observation.reason === 'match' && (observation.finalized !== true || observation.quarantined !== 'no'))) {
|
|
534
|
+
failure = new Error(`pre-cut warm comparison was not measured on attempt ${index + 1}: ${observation.reason}`);
|
|
535
|
+
}
|
|
536
|
+
} catch (error) {
|
|
537
|
+
observation = { files: [input.file], attemptId: `precut-error-${index + 1}-${randomUUID()}`,
|
|
538
|
+
status: 'unmeasured', reason: String(error), quarantined: 'unavailable' };
|
|
539
|
+
failure = error instanceof Error ? error : new Error(String(error));
|
|
540
|
+
}
|
|
541
|
+
attemptIds.add(observation.attemptId);
|
|
542
|
+
observations.push(observation);
|
|
543
|
+
recordWarmPrecutCanarySample(input.historyPath, { version: opts.version, commit: opts.commit,
|
|
544
|
+
baselineCommit: opts.baselineCommit, warmShadow: [observation] });
|
|
545
|
+
if (failure) throw failure;
|
|
546
|
+
}
|
|
547
|
+
const history = readWarmVerdictHistory(input.historyPath);
|
|
548
|
+
const evidence = warmEvidenceFromVerdicts(history, opts.version, opts.commit, opts.baselineCommit, lockHash);
|
|
549
|
+
const warmReuseConfig = (input.config && typeof input.config === 'object' && 'pod' in input.config
|
|
550
|
+
&& recordValue((input.config as Record<string, unknown>).pod)?.warmReuse) as WarmReuseSwitchConfig | undefined;
|
|
551
|
+
return { samples: observations, decision: decideWarmReuse('gate-recheck', warmReuseConfig,
|
|
552
|
+
evidence ? { 'gate-recheck': evidence } : undefined) };
|
|
553
|
+
}
|
|
399
554
|
/** Every counted match has a measured, finalized worker with this exact cut, baseline and lock. */
|
|
400
555
|
export function warmEvidenceFromVerdicts(history: readonly WarmVerdictSample[] | undefined,
|
|
401
556
|
version: string, head: string, baseline: string, lock: string): WarmReuseEvidence | undefined {
|
|
402
557
|
if (!history?.length || !lock) return undefined;
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
});
|
|
558
|
+
const matchingHistory = history.filter((verdict) => verdict.version === version && verdict.commit === head
|
|
559
|
+
&& verdict.baselineCommit === baseline);
|
|
560
|
+
const rows = matchingHistory.flatMap((verdict) => verdict.warmShadow.length ? verdict.warmShadow : [null]);
|
|
561
|
+
if (!rows.length) return undefined;
|
|
408
562
|
const attempts = new Set<string>();
|
|
409
563
|
const key = rows.at(-1)?.workerKey;
|
|
410
564
|
if (rows.some((entry) => {
|
|
@@ -807,6 +961,7 @@ export function gateShardShell(o: { parts: readonly (readonly string[])[]; cdpPa
|
|
|
807
961
|
...(o.beatSeconds ? gateBeatScript(o.beatSeconds, o.installSlots > 0) : []),
|
|
808
962
|
...(o.cachePrefix ? [o.cachePrefix] : []),
|
|
809
963
|
gateEnvBaselineLine(),
|
|
964
|
+
`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"`,
|
|
810
965
|
...(o.rulesBaseSha ? [`export ELANOUS_GATE_BASE_SHA=${quote(o.rulesBaseSha)}`] : []),
|
|
811
966
|
// Seconds since this container's PID 1 started (the clone ran before this shell) — 0 when /proc cannot tell.
|
|
812
967
|
`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)`,
|
|
@@ -1166,6 +1321,7 @@ export interface ShardArtifacts {
|
|
|
1166
1321
|
finished: string[];
|
|
1167
1322
|
/** Raw logs of the parts that did not finish (diagnostics · last started file). */
|
|
1168
1323
|
unfinishedLog?: string;
|
|
1324
|
+
runtimeIdentity?: ReturnType<typeof parseGatePodRuntimeIdentity>;
|
|
1169
1325
|
/** Original failure ids whose one same-Pod standalone retry completed cleanly. */
|
|
1170
1326
|
loadFlaky?: string[];
|
|
1171
1327
|
/** Files for which a same-Pod retry completed, even if it remained failing. */
|
|
@@ -1174,16 +1330,20 @@ export interface ShardArtifacts {
|
|
|
1174
1330
|
/** Reads a shard Pod's outbox: the part layout ({@link gateShardShell}) or the older single-run one (shard.log · shard.rc · junit.xml). */
|
|
1175
1331
|
export function readShardArtifacts(dir: string, parts: readonly (readonly string[])[]): ShardArtifacts {
|
|
1176
1332
|
const read = (name: string) => { const path = join(dir, name); return existsSync(path) ? readFileSync(path, 'utf8') : undefined; };
|
|
1333
|
+
const identityText = read('runtime-identity.json');
|
|
1334
|
+
const runtimeIdentity = identityText === undefined ? undefined : parseGatePodRuntimeIdentity(identityText) ?? undefined;
|
|
1177
1335
|
const rcOf = (text: string | undefined) => { const value = text?.trim() ?? ''; return /^\d+$/.test(value) && Number.isSafeInteger(Number(value)) ? Number(value) : undefined; };
|
|
1178
1336
|
const installRc = rcOf(read('install.rc'));
|
|
1179
1337
|
const install = read('install.log');
|
|
1180
1338
|
if (installRc === undefined && install === undefined) {
|
|
1181
1339
|
const junit = read('junit.xml');
|
|
1182
1340
|
const rc = rcOf(read('shard.rc'));
|
|
1183
|
-
return { output: read('shard.log'), ...(junit !== undefined ? { junit } : {}), ...(rc !== undefined ? { rc } : {}),
|
|
1341
|
+
return { output: read('shard.log'), ...(junit !== undefined ? { junit } : {}), ...(rc !== undefined ? { rc } : {}),
|
|
1342
|
+
...(runtimeIdentity ? { runtimeIdentity } : {}), timedOut: false, finished: [] };
|
|
1184
1343
|
}
|
|
1185
1344
|
const timedOut = read('shard.timeout') !== undefined;
|
|
1186
|
-
if (installRc !== 0) return { output: install, ...(installRc !== undefined ? { rc: installRc } : {}),
|
|
1345
|
+
if (installRc !== 0) return { output: install, ...(installRc !== undefined ? { rc: installRc } : {}),
|
|
1346
|
+
...(runtimeIdentity ? { runtimeIdentity } : {}), timedOut, finished: [] };
|
|
1187
1347
|
const good: Array<{ log: string; junit?: string; rc: 0 | 1; files: readonly string[]; loadFlaky?: string[]; loadFlakyAttemptedFiles?: string[] }> = [];
|
|
1188
1348
|
const bad: string[] = [];
|
|
1189
1349
|
const exitAnomaly: string[] = [];
|
|
@@ -1261,6 +1421,7 @@ export function readShardArtifacts(dir: string, parts: readonly (readonly string
|
|
|
1261
1421
|
...(attemptedFiles.size ? { loadFlakyAttemptedFiles: [...attemptedFiles].sort() } : {}),
|
|
1262
1422
|
...(bad.length ? { unfinishedLog: bad.join('\n') } : {}),
|
|
1263
1423
|
...(exitAnomaly.length && !otherBad ? { exitCodeOnly: exitAnomaly } : {}),
|
|
1424
|
+
...(runtimeIdentity ? { runtimeIdentity } : {}),
|
|
1264
1425
|
};
|
|
1265
1426
|
}
|
|
1266
1427
|
|
|
@@ -1447,6 +1608,7 @@ class StalledPodShards extends Error {
|
|
|
1447
1608
|
const exitCode = stalledShards.filter((item) => item.reason === 'exit-code');
|
|
1448
1609
|
super(`pod sweep incomplete: stalled shards ${stalledShards.map(({ shard }) => shard).join(', ')}`
|
|
1449
1610
|
+ (unmeasured.length ? ` (unmeasured: no test output within ${unmeasured[0]!.stall!.seconds}s of checkout — shards ${[...new Set(unmeasured.map(({ shard }) => shard))].join(', ')})` : '')
|
|
1611
|
+
+ (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('; ')})` : '')
|
|
1450
1612
|
+ (exitCode.length ? ` (exit-code anomaly: 0 fail but exit ≠ 0/1 — ${[...new Set(exitCode.flatMap(({ files }) => files))].join(', ')})` : '')
|
|
1451
1613
|
+ (abortedShards ? ` (중단 조각 ${abortedShards}: mid-run-stall)` : ''));
|
|
1452
1614
|
}
|
|
@@ -1676,7 +1838,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1676
1838
|
if (!/^[0-9a-f]{40}$/.test(base)) throw new Error(`rules gate base invalid for ${commit.slice(0, 12)}: ${base}`);
|
|
1677
1839
|
return base;
|
|
1678
1840
|
};
|
|
1679
|
-
const podSweep = async (tree: string, logDir: string | undefined, pod: NonNullable<GateOptions['pod']>, only?: readonly string[]): Promise<CommandResult> => {
|
|
1841
|
+
const podSweep = async (tree: string, logDir: string | undefined, pod: NonNullable<GateOptions['pod']>, only?: readonly string[], onProgress?: (progress: GateSweepProgress) => void): Promise<CommandResult> => {
|
|
1680
1842
|
const sweepId = randomUUID();
|
|
1681
1843
|
const shardCount = pod.shards ?? 24;
|
|
1682
1844
|
const isolationConcurrency = pod.isolationConcurrency ?? 4;
|
|
@@ -1936,6 +2098,13 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1936
2098
|
// GATE-LIVE-OBS — root rows stay stable; each recursive split/retry and tail gets its own row.
|
|
1937
2099
|
// Otherwise a finished root Job can hide a still-running child (or tail) behind 80/80 done.
|
|
1938
2100
|
const board: GateShard[] = shards.map((item, index) => ({ id: `pod-${index}`, state: 'pending', plannedMin: Math.round(item.plannedSeconds / 6) / 10 }));
|
|
2101
|
+
const reportProgress = () => onProgress?.({ executor: 'pod', groups: board.map((row) => ({
|
|
2102
|
+
id: row.id, state: row.state === 'retry' ? 'running' : row.state,
|
|
2103
|
+
...(row.host ? { host: row.host } : {}),
|
|
2104
|
+
...(row.startedAt ? { startedAtMs: Date.parse(row.startedAt) } : {}),
|
|
2105
|
+
...(row.endedAt ? { endedAtMs: Date.parse(row.endedAt) } : {}),
|
|
2106
|
+
})) });
|
|
2107
|
+
reportProgress();
|
|
1939
2108
|
const filesById = new Map(shards.map((item, index) => [`pod-${index}`, [...item.files]]));
|
|
1940
2109
|
const detailsById = new Map<string, Partial<GateShardLedgerRow>>();
|
|
1941
2110
|
const observeShard = (row: GateShard) => {
|
|
@@ -1972,6 +2141,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1972
2141
|
for (const key of clear) delete next[key];
|
|
1973
2142
|
if (JSON.stringify(next) === JSON.stringify(row)) return;
|
|
1974
2143
|
board[index] = next;
|
|
2144
|
+
reportProgress();
|
|
1975
2145
|
observeShard(next);
|
|
1976
2146
|
flushBoard();
|
|
1977
2147
|
};
|
|
@@ -2075,6 +2245,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2075
2245
|
let podStages: Record<string, number> | undefined;
|
|
2076
2246
|
let podLifecycle: Record<string, string> | undefined;
|
|
2077
2247
|
let imageDigest: string | null = null;
|
|
2248
|
+
let runtimeIdentity: ReturnType<typeof parseGatePodRuntimeIdentity> | undefined;
|
|
2078
2249
|
let podJobName: string | undefined;
|
|
2079
2250
|
let batchArtifactsComplete = true;
|
|
2080
2251
|
let junit: string | undefined;
|
|
@@ -2086,6 +2257,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2086
2257
|
let lastFile: string | undefined;
|
|
2087
2258
|
let jobFailed = false;
|
|
2088
2259
|
let jobError = '';
|
|
2260
|
+
let imagePinMismatch = false;
|
|
2089
2261
|
let failureCapture: ReturnType<NonNullable<NonNullable<GateOptions['pod']>['failureEvidence']>> | undefined;
|
|
2090
2262
|
let jobNameForCapture: string | undefined;
|
|
2091
2263
|
let jobContextForCapture: string | undefined;
|
|
@@ -2182,7 +2354,8 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2182
2354
|
pool: pod.pool, poolScheduler: trackedPool(shard, branch, jobName, resultFile, attemptId, (member) => {
|
|
2183
2355
|
stamp('poolPlaced'); jobContext = member.context; jobContextForCapture = member.context; placedOn = member.context; placed();
|
|
2184
2356
|
if (branch === '-tail') tailPlacedHost = member.context;
|
|
2185
|
-
}, skipMembers, depth === 0 && branch === '' && hostRetries === 0 && !retriedNoOutput && !batchIsolation && skipMembers.size === 0), clone: true, source: { kind: 'commit', sha: commit },
|
|
2357
|
+
}, skipMembers, depth === 0 && branch === '' && hostRetries === 0 && !retriedNoOutput && !batchIsolation && skipMembers.size === 0), clone: true, source: { kind: 'commit', sha: commit },
|
|
2358
|
+
...(pod.imagePin === 'cut-commit' ? { imageCommit: commit } : {}), deadlineSeconds: shardDeadline,
|
|
2186
2359
|
...gateHostMirror(pod),
|
|
2187
2360
|
...(multiHost ? { statusUnreadableMs: memberUnreadableSeconds * 1000 } : {}),
|
|
2188
2361
|
...(bunCache ? { bunCache } : {}),
|
|
@@ -2198,7 +2371,9 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2198
2371
|
// poolOverride is the injected scheduler used by isolated tests; it has no authenticated kubectl
|
|
2199
2372
|
// context. Those sweeps intentionally cannot mint a valid baseline receipt (missing observed digest).
|
|
2200
2373
|
// Production pool sweeps always use this Kubernetes lookup; lookup failure is receipt-invalid only.
|
|
2201
|
-
if (
|
|
2374
|
+
if (tools.podImageDigest) {
|
|
2375
|
+
imageDigest = tools.podImageDigest({ job, host: placedOn ?? 'unobserved' });
|
|
2376
|
+
} else if (!poolOverride && placedOn) {
|
|
2202
2377
|
try {
|
|
2203
2378
|
const got = kubectl(['--context', placedOn, '-n', 'elanous-test', 'get', 'pods', '-l', `job-name=${job.job}`, '-o', 'json']);
|
|
2204
2379
|
if (got.status === 0) {
|
|
@@ -2207,10 +2382,18 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2207
2382
|
}
|
|
2208
2383
|
} catch { /* timings never change the verdict */ }
|
|
2209
2384
|
}
|
|
2385
|
+
if (pod.pinnedImageDigest && imageDigest !== pod.pinnedImageDigest) {
|
|
2386
|
+
imagePinMismatch = true;
|
|
2387
|
+
debug.log('release-loop.gate', 'image-pin-mismatch', { runId: pod.pinnedRunId ?? null,
|
|
2388
|
+
shard, branch, host: placedOn ?? null, pinnedDigest: pod.pinnedImageDigest, actualDigest: imageDigest,
|
|
2389
|
+
decision: 'reject-job' }, { level: 'error' });
|
|
2390
|
+
throw new Error(`gate image pin mismatch: expected ${pod.pinnedImageDigest}, got ${imageDigest ?? 'unmeasured'}`);
|
|
2391
|
+
}
|
|
2210
2392
|
jobExitCode = job.exitCode;
|
|
2211
2393
|
if (job.exitCode !== 0) captureFailure();
|
|
2212
2394
|
batchArtifactDir = job.artifactsDir;
|
|
2213
2395
|
const read = readShardArtifacts(job.artifactsDir, parts);
|
|
2396
|
+
runtimeIdentity = read.runtimeIdentity;
|
|
2214
2397
|
if (logDir) {
|
|
2215
2398
|
mkdirSync(logDir, { recursive: true });
|
|
2216
2399
|
let retryArtifactIndex = 0;
|
|
@@ -2319,7 +2502,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2319
2502
|
...(failureCapture.logTail !== undefined ? { logTail: redactedGateLogTail(failureCapture.logTail) } : {}),
|
|
2320
2503
|
...(failureCapture.error ? { error: redactSecretText(failureCapture.error) } : {}),
|
|
2321
2504
|
} } : {}),
|
|
2322
|
-
...(imageDigest ? { imageDigest } : {}),
|
|
2505
|
+
...(imageDigest ? { imageDigest } : {}), ...(runtimeIdentity ? { runtimeIdentity } : {}),
|
|
2323
2506
|
...(fileSeconds !== undefined ? { fileSeconds, testSeconds } : {}), ...(testWallSeconds !== undefined ? { testWallSeconds } : {}),
|
|
2324
2507
|
...(podOverheadSeconds !== undefined ? { podOverheadSeconds } : {}),
|
|
2325
2508
|
...(unfinishedLogBytes !== undefined ? { unfinishedLogBytes } : {}),
|
|
@@ -2353,6 +2536,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2353
2536
|
let namedFailures: number | undefined;
|
|
2354
2537
|
let unattributedDetail: string | undefined;
|
|
2355
2538
|
if (descent === 'split') reason = 'incomplete';
|
|
2539
|
+
else if (imagePinMismatch) reason = 'image-pin-mismatch';
|
|
2356
2540
|
// GATE-POD-STALL-FAILFAST: stopped with no test output — the existing «no output» reason (one fresh retry).
|
|
2357
2541
|
else if (stall) reason = 'no-output';
|
|
2358
2542
|
else if (timedOut) reason = 'timeout';
|
|
@@ -2471,6 +2655,8 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2471
2655
|
}
|
|
2472
2656
|
}
|
|
2473
2657
|
if (reason) {
|
|
2658
|
+
if (reason === 'image-pin-mismatch') return { runs: [], stalled: [{ shard, files: paths, reason,
|
|
2659
|
+
detail: jobError.slice(0, 300), ...(placedOn ? { lastHost: placedOn } : {}) }] };
|
|
2474
2660
|
// GATE-SKIP-KNOWN-RETRY — a shard whose summary is complete but whose failures could not be read back (unattributed)
|
|
2475
2661
|
// and whose every named failure is one the previous release already had is not re-run: re-running only re-measures
|
|
2476
2662
|
// known failures (0.2.18 cut: 109 retry Pods · 1,840 shard-minutes, 4x the first pass). Those failures go to the
|
|
@@ -2623,7 +2809,10 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2623
2809
|
// has no base/environment/image binding and cannot authorize this cross-host measurement.
|
|
2624
2810
|
// The sixth runShardImpl argument is `fresh`: a missing receipt must launch a new Job.
|
|
2625
2811
|
const freshForReceipt = !!receiptRoot || pod.freshShards === true;
|
|
2626
|
-
const done = await withRootSlot(() =>
|
|
2812
|
+
const done = await withRootSlot(() => {
|
|
2813
|
+
markShard(shard, { state: 'running', startedAt: new Date().toISOString() }, ['waitReason']);
|
|
2814
|
+
return runShard(item.files, shard, 0, '', false, freshForReceipt);
|
|
2815
|
+
});
|
|
2627
2816
|
shardDurations[shard] = clock() - shardStartedAt;
|
|
2628
2817
|
const state = !done.stalled.length ? 'done' as const : done.stalled.some((stall) => stall.reason === 'timeout') ? 'timeout' as const : 'failed' as const;
|
|
2629
2818
|
markShard(shard, { state, rc: done.stalled.length || done.runs.some((run) => run.fail || run.errors) ? 1 : 0, endedAt: new Date().toISOString() }, ['waitReason']);
|
|
@@ -2815,6 +3004,33 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2815
3004
|
await quarantine('gate-job-failed');
|
|
2816
3005
|
throw error;
|
|
2817
3006
|
}
|
|
3007
|
+
if (pod.pinnedImageDigest) {
|
|
3008
|
+
let actualDigest: string | null = null;
|
|
3009
|
+
if (tools.podImageDigest) {
|
|
3010
|
+
actualDigest = tools.podImageDigest({ job, host: producer?.context ?? 'unobserved' });
|
|
3011
|
+
} else if (!poolOverride) {
|
|
3012
|
+
// Rechecks launch through the pool scheduler too; find the Job in its configured
|
|
3013
|
+
// contexts so a recheck cannot silently escape the run-level image pin.
|
|
3014
|
+
const contexts = [...new Set(parsePodPool(pod.pool).map((member) => member.context))];
|
|
3015
|
+
const observed: string[] = [];
|
|
3016
|
+
for (const context of contexts) {
|
|
3017
|
+
try {
|
|
3018
|
+
const got = kubectl(['--context', context, '-n', 'elanous-test', 'get', 'pods', '-l', `job-name=${job.job}`, '-o', 'json']);
|
|
3019
|
+
if (got.status !== 0) continue;
|
|
3020
|
+
const digest = actualGatePodImageDigest(got.stdout);
|
|
3021
|
+
if (digest) observed.push(digest);
|
|
3022
|
+
} catch { /* inability to observe is rejected below */ }
|
|
3023
|
+
}
|
|
3024
|
+
if (new Set(observed).size === 1) actualDigest = observed[0]!;
|
|
3025
|
+
}
|
|
3026
|
+
if (actualDigest !== pod.pinnedImageDigest) {
|
|
3027
|
+
debug.log('release-loop.gate', 'image-pin-mismatch', { runId: pod.pinnedRunId ?? null,
|
|
3028
|
+
job: job.job, phase: 'recheck', host: producer?.context ?? null,
|
|
3029
|
+
pinnedDigest: pod.pinnedImageDigest, actualDigest, decision: 'reject-job' }, { level: 'error' });
|
|
3030
|
+
await quarantine('gate-image-pin-mismatch');
|
|
3031
|
+
throw new Error(`gate image pin mismatch: expected ${pod.pinnedImageDigest}, got ${actualDigest ?? 'unmeasured'} (recheck)`);
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
2818
3034
|
try {
|
|
2819
3035
|
const artifact = (name: string) => {
|
|
2820
3036
|
const path = join(job.artifactsDir, name);
|
|
@@ -2959,8 +3175,9 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2959
3175
|
recheckPodBatch: (files, commit, pod, timeoutMs) => runPodRecheckBatch(files, commit, pod, timeoutMs),
|
|
2960
3176
|
get remoteMirror() { return remoteMirror; },
|
|
2961
3177
|
set remoteMirror(path) { remoteMirror = path; },
|
|
2962
|
-
|
|
2963
|
-
|
|
3178
|
+
progressScheduler: tools.progressScheduler ?? scheduleGateProgress,
|
|
3179
|
+
async sweep(tree, logDir, pod, only, onProgress) {
|
|
3180
|
+
if (pod) return podSweep(tree, logDir, pod, only, onProgress);
|
|
2964
3181
|
const listed = await command('git', ['ls-files', '*.test.*'], tree);
|
|
2965
3182
|
check(listed, 'git ls-files tests');
|
|
2966
3183
|
const allFiles = listed.output.split(/\r?\n/).filter((file) => /\.test\.(?:tsx?|jsx?|mts|cts)$/.test(file));
|
|
@@ -2982,13 +3199,26 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
2982
3199
|
} else cdpPatterns = deriveCdpTestPatterns({ cwd: tree });
|
|
2983
3200
|
const outputs: string[] = [];
|
|
2984
3201
|
let total = { pass: 0, fail: 0, errors: 0, ran: 0, files: 0 };
|
|
2985
|
-
|
|
2986
|
-
|
|
3202
|
+
const selectedGroups = groups.filter((group) => group.paths.length && files.some((f) => group.paths.some((p) => f === p || f.startsWith(`${p}/`))));
|
|
3203
|
+
const progressGroups: GateProgressGroup[] = selectedGroups.map((group) => ({ id: group.name, state: 'pending', ...(remote ? { host: remote } : {}) }));
|
|
3204
|
+
onProgress?.({ executor: 'host', groups: progressGroups });
|
|
3205
|
+
for (const group of selectedGroups) {
|
|
3206
|
+
const progress = progressGroups.find((item) => item.id === group.name)!;
|
|
3207
|
+
progress.state = 'running'; progress.startedAtMs = clock();
|
|
3208
|
+
onProgress?.({ executor: 'host', groups: progressGroups });
|
|
2987
3209
|
const start = Date.now();
|
|
2988
3210
|
const groupIgnores = [...new Set(cdpPatterns)]
|
|
2989
3211
|
.filter((pattern) => files.includes(pattern) && group.paths.some((p) => pattern === p || pattern.startsWith(`${p}/`)))
|
|
2990
3212
|
.flatMap((pattern) => ['--path-ignore-patterns', pattern]);
|
|
2991
|
-
|
|
3213
|
+
let run: CommandResult;
|
|
3214
|
+
try { run = await command('bun', ['run', 'test:deterministic', ...groupIgnores, ...group.paths.map(asPath)], tree); }
|
|
3215
|
+
catch (error) {
|
|
3216
|
+
progress.state = 'failed'; progress.endedAtMs = clock();
|
|
3217
|
+
onProgress?.({ executor: 'host', groups: progressGroups });
|
|
3218
|
+
throw error;
|
|
3219
|
+
}
|
|
3220
|
+
progress.state = run.timedOut ? 'timeout' : run.rc === 0 ? 'done' : 'failed'; progress.endedAtMs = clock();
|
|
3221
|
+
onProgress?.({ executor: 'host', groups: progressGroups });
|
|
2992
3222
|
if (logDir) {
|
|
2993
3223
|
mkdirSync(logDir, { recursive: true });
|
|
2994
3224
|
writeFileSync(join(logDir, `${group.name}.log`), run.output, { mode: 0o600 });
|
|
@@ -3120,7 +3350,8 @@ export function saveBaselineFileCache(ledger: string, commit: string, rows: Base
|
|
|
3120
3350
|
}
|
|
3121
3351
|
|
|
3122
3352
|
export async function judgeGate(opts: GateOptions, runner: GateRunner = createGateRunner(opts.repo ?? process.cwd(), opts.remote),
|
|
3123
|
-
imageProbe: typeof preflightGateImageDigest = preflightGateImageDigest
|
|
3353
|
+
imageProbe: typeof preflightGateImageDigest = preflightGateImageDigest,
|
|
3354
|
+
identityProbe: typeof preflightGateExecutionIdentity = preflightGateExecutionIdentity): Promise<GateResult> {
|
|
3124
3355
|
if (opts.pod?.warmReplay) opts = { ...opts, pod: { ...opts.pod,
|
|
3125
3356
|
warmShadowScope: { requested: false, attempts: 0 } } };
|
|
3126
3357
|
const explicitConfig = getElanousConfigDirOverride();
|
|
@@ -3163,22 +3394,92 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
3163
3394
|
let cutSweepRc: number | null = null;
|
|
3164
3395
|
let radiusShadow: GateRadiusShadowPlan | undefined;
|
|
3165
3396
|
const trees: string[] = [];
|
|
3397
|
+
const baselineVersion = opts.baselineVersion;
|
|
3166
3398
|
try {
|
|
3167
3399
|
if (!sha.test(opts.commit) || !versionPattern.test(opts.version)
|
|
3168
3400
|
|| (opts.baselineVersion && !versionPattern.test(opts.baselineVersion))
|
|
3169
3401
|
|| (opts.baselineCommit && !sha.test(opts.baselineCommit))) throw new Error('invalid commit or version');
|
|
3170
|
-
if (!
|
|
3402
|
+
if (!baselineVersion) throw new Error('previous release version required (--baseline-version)');
|
|
3171
3403
|
if (opts.rehearsalBaseline && (opts.graphRunId || !opts.pod || !/-rc\.\d+$/.test(opts.version)))
|
|
3172
3404
|
throw new Error('rehearsal baseline requires a direct Pod gate with an isolated rc version');
|
|
3173
3405
|
// Observation only. Failure to plan cannot change the gate selector or verdict.
|
|
3174
3406
|
try { radiusShadow = planGateRadiusShadow({ repo, ledgerRoot: ledger, version: opts.version,
|
|
3175
|
-
baselineVersion
|
|
3407
|
+
baselineVersion, forCommit: opts.commit, instanceRoot: root,
|
|
3176
3408
|
expectedBaselineCommit: opts.baselineCommit }); }
|
|
3177
3409
|
catch (error) { debug.log('release-loop.gate', 'radius-shadow-unavailable', { reason: String(error) }); }
|
|
3178
3410
|
if (!Number.isSafeInteger(opts.recheckFileLimit ?? GATE_HOST_RECHECK_FILE_LIMIT) || (opts.recheckFileLimit ?? GATE_HOST_RECHECK_FILE_LIMIT) < 1)
|
|
3179
3411
|
throw new Error('invalid gate recheck file limit');
|
|
3180
3412
|
if (opts.pod && (!opts.pod.pool || !Number.isSafeInteger(opts.pod.shards ?? 24) || (opts.pod.shards ?? 24) < 1
|
|
3181
3413
|
|| !Number.isSafeInteger(opts.pod.shardTimeoutSeconds ?? 1200) || (opts.pod.shardTimeoutSeconds ?? 1200) < 1)) throw new Error('invalid pod sweep options');
|
|
3414
|
+
if (opts.pod && opts.graphRunId) {
|
|
3415
|
+
const components = opts.pod.pool.split(',').map((part) => part.trim()).filter(Boolean);
|
|
3416
|
+
const members = parsePodPool(opts.pod.pool);
|
|
3417
|
+
const memberSpecs = members.map((member) => {
|
|
3418
|
+
const spec = components.find((part) => part.split(/[@:#]/, 1)[0] === member.context);
|
|
3419
|
+
if (!spec) throw new Error(`gate image pin cannot map pool member ${member.context}`);
|
|
3420
|
+
return { context: member.context, spec };
|
|
3421
|
+
});
|
|
3422
|
+
const poolImages = new Map<string, readonly string[]>();
|
|
3423
|
+
const currentImages: Record<string, string | null> = {};
|
|
3424
|
+
for (const member of memberSpecs) {
|
|
3425
|
+
const digest = imageProbe(member.spec, opts.commit);
|
|
3426
|
+
currentImages[member.context] = digest;
|
|
3427
|
+
poolImages.set(member.context, digest ? [digest] : []);
|
|
3428
|
+
}
|
|
3429
|
+
const pinRunId = opts.graphRunId;
|
|
3430
|
+
const pinPath = gateImagePinPath(root, opts.version, pinRunId);
|
|
3431
|
+
const gateImageTag = `${podCommandImageFor([], true).split(':')[0]}:${opts.commit.slice(0, 12)}`;
|
|
3432
|
+
let savedPin: ReturnType<typeof readGateImagePin>;
|
|
3433
|
+
try { savedPin = existsSync(pinPath) ? readGateImagePin(pinPath, pinRunId, gateImageTag, opts.commit) : null; }
|
|
3434
|
+
catch (error) {
|
|
3435
|
+
debug.log('release-loop.gate', 'image-pin', { runId: pinRunId, currentImages, decision: 'fail-closed',
|
|
3436
|
+
reason: 'saved-image-pin-invalid', error: String(error).slice(0, 300) }, { level: 'error' });
|
|
3437
|
+
throw new Error('gate resume refused: saved image digest pin is invalid');
|
|
3438
|
+
}
|
|
3439
|
+
const savedDigest = savedPin?.digest ?? null;
|
|
3440
|
+
let previouslyExecuted = false;
|
|
3441
|
+
if (opts.graphRunId) {
|
|
3442
|
+
// A gate's immutable pin is written before any Pod work. The graph state is only a legacy-run detector:
|
|
3443
|
+
// if it already records an executed gate but this pin does not exist, resume cannot safely invent one.
|
|
3444
|
+
const runPath = join(root, 'graph-runs', 'release-loop', `${opts.graphRunId}.json`);
|
|
3445
|
+
if (existsSync(runPath)) {
|
|
3446
|
+
try {
|
|
3447
|
+
const prior = JSON.parse(readFileSync(runPath, 'utf8')) as { nodes?: Array<{ nodeId?: unknown; executed?: unknown }> };
|
|
3448
|
+
previouslyExecuted = prior.nodes?.some((node) => node.nodeId === 'gate' && node.executed === true) ?? false;
|
|
3449
|
+
} catch { previouslyExecuted = true; }
|
|
3450
|
+
}
|
|
3451
|
+
}
|
|
3452
|
+
if (!savedDigest && previouslyExecuted) {
|
|
3453
|
+
debug.log('release-loop.gate', 'image-pin', { runId: opts.graphRunId, savedDigest: null, currentImages,
|
|
3454
|
+
decision: 'fail-closed', reason: 'resume-pin-missing' }, { level: 'error' });
|
|
3455
|
+
throw new Error('gate resume refused: saved image digest pin is missing');
|
|
3456
|
+
}
|
|
3457
|
+
const resolution = resolveGateImagePin({ ...(savedDigest ? { savedDigest } : {}),
|
|
3458
|
+
...(!savedDigest ? { initialDigest: [...poolImages.values()].flat().find(Boolean) ?? null } : {}), poolImages });
|
|
3459
|
+
if (!resolution.ok) {
|
|
3460
|
+
debug.log('release-loop.gate', 'image-pin', { runId: opts.graphRunId ?? null, savedDigest, currentImages,
|
|
3461
|
+
decision: 'fail-closed', reason: resolution.reason }, { level: 'error' });
|
|
3462
|
+
throw new Error(`gate image pin refused: ${resolution.reason}`);
|
|
3463
|
+
}
|
|
3464
|
+
const selected = memberSpecs.filter((member) => resolution.eligiblePools.includes(member.context));
|
|
3465
|
+
if (!selected.length) throw new Error('gate image pin refused: no eligible pool');
|
|
3466
|
+
if (!savedDigest) {
|
|
3467
|
+
try { writeGateImagePin(pinPath, pinRunId, resolution.digest, gateImageTag, opts.commit); }
|
|
3468
|
+
catch (error) {
|
|
3469
|
+
debug.log('release-loop.gate', 'image-pin', { runId: pinRunId, pinnedDigest: resolution.digest,
|
|
3470
|
+
currentImages, decision: 'fail-closed', reason: 'pin-write-failed', error: String(error).slice(0, 300) }, { level: 'error' });
|
|
3471
|
+
throw error;
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
opts = { ...opts, pod: { ...opts.pod, pool: selected.map((member) => member.spec).join(','),
|
|
3475
|
+
pinnedImageDigest: resolution.digest, pinnedRunId: pinRunId } };
|
|
3476
|
+
debug.log('release-loop.gate', 'image-pin', { runId: opts.graphRunId ?? null, pinnedDigest: resolution.digest,
|
|
3477
|
+
currentImages, eligiblePools: resolution.eligiblePools, excludedPools: members.map((member) => member.context)
|
|
3478
|
+
.filter((context) => !resolution.eligiblePools.includes(context)), decision: savedDigest ? 'resume-pinned' : 'pin-created' });
|
|
3479
|
+
} else if (opts.pod) {
|
|
3480
|
+
debug.log('release-loop.gate', 'image-pin-unscoped', { runId: null, decision: 'not-applicable',
|
|
3481
|
+
reason: 'direct-pod-gate-without-graph-run-id' });
|
|
3482
|
+
}
|
|
3182
3483
|
if (opts.remote && !/^(?:[\w.-]+@)?[\w.-]+$/.test(opts.remote)) throw new Error('invalid ssh host');
|
|
3183
3484
|
if (opts.remote) {
|
|
3184
3485
|
runner.remoteMirror = undefined;
|
|
@@ -3189,8 +3490,8 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
3189
3490
|
const homePath = home?.output.split(/\r?\n/).find((line) => line.startsWith('/'));
|
|
3190
3491
|
if (home && (!homePath || !/^\/[^\r\n]*$/.test(homePath))) throw new Error('invalid remote home');
|
|
3191
3492
|
const mirror = configured.startsWith('~/') ? join(homePath!, configured.slice(2)) : configured;
|
|
3192
|
-
const baseSha = opts.baselineCommit ?? baselineReleaseCommit(
|
|
3193
|
-
cachedBaseline(
|
|
3493
|
+
const baseSha = opts.baselineCommit ?? baselineReleaseCommit(baselineVersion);
|
|
3494
|
+
cachedBaseline(baselineVersion, baseSha);
|
|
3194
3495
|
const present = await runner.command('test', ['-d', mirror], '/tmp');
|
|
3195
3496
|
if (present.rc === 1) {
|
|
3196
3497
|
check(await runner.command('mkdir', ['-p', dirname(mirror)], '/tmp'), 'remote mirror parent');
|
|
@@ -3219,26 +3520,57 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
3219
3520
|
// GATE-PARTIAL: a plan made by `release resume --from gate --partial` for exactly this commit re-runs only its files.
|
|
3220
3521
|
// Its logs go to cut-partial/ — the full cut/ logs stay whole (the next release reads them as a baseline source).
|
|
3221
3522
|
const partialRead = readPartialPlanResult(roots, opts.version, opts.commit);
|
|
3222
|
-
const explicitPartial = partialRead.plan;
|
|
3523
|
+
const explicitPartial = opts.full ? null : partialRead.plan;
|
|
3223
3524
|
const rehearsalStampPresent = ['hard-5h', 'hard-mid'].some((step) =>
|
|
3224
3525
|
existsSync(join(root, 'release', opts.version, 'rehearsal', `${step}.json`)));
|
|
3225
|
-
const
|
|
3226
|
-
?
|
|
3526
|
+
const currentIdentity = !opts.full && !explicitPartial && opts.graphRunId && opts.pod && rehearsalStampPresent
|
|
3527
|
+
? identityProbe(opts.pod.pool, process.env, opts.commit) : null;
|
|
3528
|
+
const currentImage = currentIdentity?.imageDigest ?? (!opts.full && !explicitPartial
|
|
3529
|
+
&& opts.graphRunId && opts.pod && rehearsalStampPresent ? imageProbe(opts.pod.pool, opts.commit) : null);
|
|
3227
3530
|
const receiptImage = currentImage ?? (opts.graphRunId && opts.pod && process.env.ELANOUS_GRAPH_CONTEXT
|
|
3228
|
-
? imageProbe(opts.pod.pool) : null);
|
|
3229
|
-
const receiptBase = opts.baselineCommit ?? (
|
|
3230
|
-
existsSync(join(location, 'release',
|
|
3231
|
-
? baselineReleaseCommit(
|
|
3232
|
-
const radius = !explicitPartial && opts.graphRunId && opts.pod && rehearsalStampPresent
|
|
3531
|
+
? imageProbe(opts.pod.pool, opts.commit) : null);
|
|
3532
|
+
const receiptBase = opts.baselineCommit ?? (roots.some((location) =>
|
|
3533
|
+
existsSync(join(location, 'release', baselineVersion, 'release.json')))
|
|
3534
|
+
? baselineReleaseCommit(baselineVersion) : undefined);
|
|
3535
|
+
const radius = !opts.full && !explicitPartial && opts.graphRunId && opts.pod && rehearsalStampPresent
|
|
3233
3536
|
? planHardRehearsalRadius({ root, repo, version: opts.version, forCommit: opts.commit,
|
|
3234
|
-
graphPins: gateGraphPins(root, opts.graphRunId), envFingerprint: gateEnvFingerprint(process.env), imageDigest: currentImage
|
|
3537
|
+
graphPins: gateGraphPins(root, opts.graphRunId), envFingerprint: gateEnvFingerprint(process.env), imageDigest: currentImage,
|
|
3538
|
+
executionIdentity: currentIdentity })
|
|
3235
3539
|
: null;
|
|
3236
3540
|
if (radius) debug.log('release-loop.gate', 'radius-live-plan', { version: opts.version, reason: radius.reason,
|
|
3237
3541
|
files: radius.plan?.files.length ?? null, rehearsalRunId: radius.rehearsalRunId ?? null });
|
|
3238
|
-
let partial = explicitPartial ?? radius?.plan ?? null;
|
|
3542
|
+
let partial = opts.full ? null : explicitPartial ?? radius?.plan ?? null;
|
|
3543
|
+
let trainReceiptPlan: ReturnType<typeof planGateTrainReceiptCarry> = null;
|
|
3544
|
+
if (!opts.full && !partial && opts.graphRunId && opts.pod) {
|
|
3545
|
+
const receiptPath = gateReceiptRegistryPath(repo);
|
|
3546
|
+
const bun = spawnSync('bun', ['--version'], { cwd: repo, encoding: 'utf8', maxBuffer: SPAWN_SYNC_MAX_BUFFER });
|
|
3547
|
+
if (receiptPath && bun.status === 0 && !bun.error) {
|
|
3548
|
+
try {
|
|
3549
|
+
trainReceiptPlan = planGateTrainReceiptCarry({ repo, commit: opts.commit, receiptPath,
|
|
3550
|
+
bunVersion: bun.stdout.trim(), lockfileHash: gateLockfileHash(repo), envFingerprint: gateReceiptEnvFingerprint(process.env) });
|
|
3551
|
+
if (trainReceiptPlan?.carriedFiles.length) partial = { version: opts.version, priorCommit: trainReceiptPlan.priorCommit,
|
|
3552
|
+
forCommit: opts.commit, files: trainReceiptPlan.files, priorFailures: [], priorErrors: [] };
|
|
3553
|
+
} catch (error) {
|
|
3554
|
+
debug.log('release-loop.gate', 'train-receipt-plan-failed', { version: opts.version, reason: String(error).slice(0, 240) });
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
if (trainReceiptPlan) {
|
|
3558
|
+
result.trainReceiptReuse = { carried: trainReceiptPlan.carriedFiles.length, total: trainReceiptPlan.totalFiles,
|
|
3559
|
+
receipts: trainReceiptPlan.receipts.length, reasons: trainReceiptPlan.reasons };
|
|
3560
|
+
debug.log('release-loop.gate', 'train-receipt-plan', { version: opts.version, commit: opts.commit,
|
|
3561
|
+
carried: trainReceiptPlan.carriedFiles.length, total: trainReceiptPlan.totalFiles,
|
|
3562
|
+
ratio: trainReceiptPlan.carriedFiles.length / trainReceiptPlan.totalFiles,
|
|
3563
|
+
receipts: trainReceiptPlan.receipts, selectedFiles: trainReceiptPlan.files.length,
|
|
3564
|
+
reasons: trainReceiptPlan.reasons, action: trainReceiptPlan.carriedFiles.length ? 'run-remaining-tests' : 'full-sweep' });
|
|
3565
|
+
} else if (!opts.full && !explicitPartial && !radius?.plan) {
|
|
3566
|
+
debug.log('release-loop.gate', 'train-receipt-plan', { version: opts.version, commit: opts.commit,
|
|
3567
|
+
carried: 0, total: null, ratio: 0, receipts: [], selectedFiles: null,
|
|
3568
|
+
reasons: ['no safe receipt coverage'], action: 'full-sweep' });
|
|
3569
|
+
}
|
|
3570
|
+
}
|
|
3239
3571
|
const explicitPodPlan = !!explicitPartial && explicitPartial.kind !== 'recheck' && !!opts.pod;
|
|
3240
3572
|
const explicitPodDigest = explicitPodPlan ? explicitPartial!.shardImageDigests?.[0] : undefined;
|
|
3241
|
-
const probedPodDigest = explicitPodDigest ? imageProbe(opts.pod!.pool) : null;
|
|
3573
|
+
const probedPodDigest = explicitPodDigest ? imageProbe(opts.pod!.pool, opts.commit) : null;
|
|
3242
3574
|
if (explicitPodPlan && (!explicitPodDigest || probedPodDigest !== explicitPodDigest)) {
|
|
3243
3575
|
debug.log('release-loop.gate', 'partial-image-preflight-fallback', { version: opts.version,
|
|
3244
3576
|
reason: !explicitPodDigest || !probedPodDigest ? 'executed-pod-image-digest-missing' : 'executed-pod-image-digest-mismatch' }, { level: 'warn' });
|
|
@@ -3274,17 +3606,35 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
3274
3606
|
let knownFailures: string[] = [];
|
|
3275
3607
|
if (opts.pod) {
|
|
3276
3608
|
try {
|
|
3277
|
-
const knownSha = opts.baselineCommit ?? baselineReleaseCommit(
|
|
3278
|
-
const saved = cachedBaseline(
|
|
3279
|
-
const recorded = saved ? undefined : baselineFromCutLogs(join(ledger, 'release',
|
|
3609
|
+
const knownSha = opts.baselineCommit ?? baselineReleaseCommit(baselineVersion);
|
|
3610
|
+
const saved = cachedBaseline(baselineVersion, knownSha)?.saved;
|
|
3611
|
+
const recorded = saved ? undefined : baselineFromCutLogs(join(ledger, 'release', baselineVersion, 'gate-logs', 'cut'), knownSha);
|
|
3280
3612
|
const trustedKnown = saved ?? (recorded?.complete ? recorded : undefined);
|
|
3281
3613
|
if (trustedKnown) knownFailures = [...trustedKnown.failures, ...(trustedKnown.errors ?? []), ...loadEnvKnownFailures(repo)];
|
|
3282
3614
|
} catch (error) { debug.log('release-loop.gate', 'known-failures-unavailable', { error: String(error) }); }
|
|
3283
3615
|
}
|
|
3284
|
-
const durationSource = join(ledger, 'release',
|
|
3285
|
-
const runCut = (selected: typeof partial) =>
|
|
3616
|
+
const durationSource = join(ledger, 'release', baselineVersion, 'gate-logs', 'cut');
|
|
3617
|
+
const runCut = async (selected: typeof partial) => {
|
|
3618
|
+
if (selected?.kind === 'carry') return undefined;
|
|
3619
|
+
const progressFile = opts.graphRunId && /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(opts.graphRunId)
|
|
3620
|
+
? join(ledger, 'graph-runs', 'release-loop', `${opts.graphRunId}.json.contexts`, 'gate-progress.jsonl') : undefined;
|
|
3621
|
+
let latest: GateSweepProgress | undefined;
|
|
3622
|
+
const emitProgress = () => {
|
|
3623
|
+
if (!latest) return;
|
|
3624
|
+
const nowMs = runner.now?.() ?? Date.now();
|
|
3625
|
+
const event = { version: opts.version, runId: opts.graphRunId ?? null, at: new Date(nowMs).toISOString(),
|
|
3626
|
+
...summarizeGateProgress({ executor: latest.executor, groups: latest.groups, nowMs }) };
|
|
3627
|
+
debug.log('release-loop.gate', 'progress', event);
|
|
3628
|
+
if (progressFile) {
|
|
3629
|
+
try { mkdirSync(dirname(progressFile), { recursive: true }); appendFileSync(progressFile, `${JSON.stringify(event)}\n`, { mode: 0o600 }); }
|
|
3630
|
+
catch (error) { debug.log('release-loop.gate', 'progress-log-write-failed', { path: progressFile, error: String(error) }, { level: 'warn' }); }
|
|
3631
|
+
}
|
|
3632
|
+
};
|
|
3633
|
+
const cancel = (runner.progressScheduler ?? scheduleGateProgress)(emitProgress, 60_000);
|
|
3634
|
+
try {
|
|
3635
|
+
const sweepResult = await runner.sweep(cutTree,
|
|
3286
3636
|
join(root, 'release', opts.version, 'gate-logs', selected ? 'cut-partial' : 'cut'),
|
|
3287
|
-
opts.pod ? { ...opts.pod, durationSource, ...(knownFailures.length ? { knownFailures } : {}), onSelectionTimed: ({ ms }) => { result.selectionMs = ms; },
|
|
3637
|
+
opts.pod ? { ...opts.pod, ...(opts.imagePin ? { imagePin: opts.imagePin } : {}), durationSource, ...(knownFailures.length ? { knownFailures } : {}), onSelectionTimed: ({ ms }) => { result.selectionMs = ms; },
|
|
3288
3638
|
// GATE-RISK-LANE — the watch list is read from this gate's own ledgers (the instance root ⊕ the machine ledger);
|
|
3289
3639
|
// the cut sweep puts those files first, the baseline sweep keeps its plan.
|
|
3290
3640
|
riskWatch: { instanceRoots: roots, ledgerRoots: roots },
|
|
@@ -3295,7 +3645,11 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
3295
3645
|
receiptRoot: join(ledger, 'release', opts.version, 'gate-logs', 'cut', 'receipts'),
|
|
3296
3646
|
base40: receiptBase,
|
|
3297
3647
|
expectedImageDigest: receiptImage } } : undefined,
|
|
3298
|
-
selected?.files);
|
|
3648
|
+
selected?.files, (progress) => { latest = progress; emitProgress(); });
|
|
3649
|
+
emitProgress();
|
|
3650
|
+
return sweepResult;
|
|
3651
|
+
} finally { cancel?.(); }
|
|
3652
|
+
};
|
|
3299
3653
|
let cutRun = await runCut(partial);
|
|
3300
3654
|
const radiusSelectionMatched = partial?.files && cutRun?.podSweep?.files
|
|
3301
3655
|
&& JSON.stringify([...partial.files].sort()) === JSON.stringify([...cutRun.podSweep.files].sort());
|
|
@@ -3376,13 +3730,13 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
3376
3730
|
if (!work) throw new Error('gate workspace missing before baseline attribution recheck');
|
|
3377
3731
|
const recheckTree = join(work, 'baseline-attribution-recheck');
|
|
3378
3732
|
trees.push(recheckTree);
|
|
3379
|
-
const baseShaForRecheck = opts.baselineCommit ?? baselineReleaseCommit(
|
|
3733
|
+
const baseShaForRecheck = opts.baselineCommit ?? baselineReleaseCommit(baselineVersion);
|
|
3380
3734
|
await runner.add(recheckTree, baseShaForRecheck);
|
|
3381
3735
|
for (const dir of [recheckTree, join(recheckTree, 'apps/pwa')])
|
|
3382
3736
|
check(await runner.command('bun', ['install'], dir), `bun install ${dir}`);
|
|
3383
3737
|
const rechecked = await runner.sweep(recheckTree,
|
|
3384
3738
|
join(root, 'release', opts.version, 'gate-logs', 'baseline-attribution-recheck'),
|
|
3385
|
-
opts.pod ? { ...opts.pod, durationSource } : undefined,
|
|
3739
|
+
opts.pod ? { ...opts.pod, ...(opts.imagePin ? { imagePin: opts.imagePin } : {}), durationSource } : undefined,
|
|
3386
3740
|
targetFiles);
|
|
3387
3741
|
if (rechecked.timedOut || rechecked.abortedShards || rechecked.stalledEnv?.length) {
|
|
3388
3742
|
attribution.baselineResult = 'timeout';
|
|
@@ -3455,13 +3809,13 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
3455
3809
|
}
|
|
3456
3810
|
if (partial) result.partial = { priorCommit: partial.priorCommit, files: partial.files.length };
|
|
3457
3811
|
cutFailures = cut;
|
|
3458
|
-
const baseSha = opts.baselineCommit ?? baselineReleaseCommit(
|
|
3812
|
+
const baseSha = opts.baselineCommit ?? baselineReleaseCommit(baselineVersion);
|
|
3459
3813
|
baselineCommitForVerdict = baseSha;
|
|
3460
|
-
const cached = cachedBaseline(
|
|
3461
|
-
const recorded = cached ? undefined : baselineFromCutLogs(join(ledger, 'release',
|
|
3814
|
+
const cached = cachedBaseline(baselineVersion, baseSha);
|
|
3815
|
+
const recorded = cached ? undefined : baselineFromCutLogs(join(ledger, 'release', baselineVersion, 'gate-logs', 'cut'), baseSha);
|
|
3462
3816
|
const trusted = cached?.saved ?? (recorded?.complete ? recorded : undefined);
|
|
3463
3817
|
result.baselineSource = cached ? cached.source : recorded?.complete ? 'cut-logs' : 'swept';
|
|
3464
|
-
debug.log('release-loop.gate', 'baseline', { version:
|
|
3818
|
+
debug.log('release-loop.gate', 'baseline', { version: baselineVersion, source: result.baselineSource, commit: baseSha });
|
|
3465
3819
|
let baseTree: string | undefined;
|
|
3466
3820
|
const getBaseTree = async () => {
|
|
3467
3821
|
if (!baseTree) {
|
|
@@ -3492,12 +3846,12 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
3492
3846
|
present = cutFiles.filter((file) => known.has(file));
|
|
3493
3847
|
}
|
|
3494
3848
|
result.baselineDeferred = { cutFiles: cutFiles.length, swept: present.length };
|
|
3495
|
-
debug.log('release-loop.gate', 'baseline-deferred', { version:
|
|
3849
|
+
debug.log('release-loop.gate', 'baseline-deferred', { version: baselineVersion, commit: baseSha, cutFiles: cutFiles.length, swept: present.length });
|
|
3496
3850
|
if (present.length) {
|
|
3497
3851
|
// 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
|
|
3498
3852
|
// after a 23-minute Pod cut).
|
|
3499
3853
|
const baseRun = await runner.sweep(baseTree!, join(root, 'release', opts.version, 'gate-logs', 'baseline'),
|
|
3500
|
-
opts.pod ? { ...opts.pod, durationSource } : undefined,
|
|
3854
|
+
opts.pod ? { ...opts.pod, ...(opts.imagePin ? { imagePin: opts.imagePin } : {}), durationSource } : undefined,
|
|
3501
3855
|
present);
|
|
3502
3856
|
baseline = await localStalled(baseRun, baseTree!, baseSha, 'baseline');
|
|
3503
3857
|
} else baseline = { failures: [], errors: [] };
|
|
@@ -3948,6 +4302,14 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
|
|
|
3948
4302
|
const fromGraph: Record<string, unknown> = context?.input ?? {};
|
|
3949
4303
|
const graphCommit = context?.outputs['version-release']?.commit;
|
|
3950
4304
|
if (typeof graphCommit === 'string') fromGraph.commit = graphCommit;
|
|
4305
|
+
if (fromGraph.gateImagePin !== undefined && fromGraph.gateImagePin !== 'cut-commit') {
|
|
4306
|
+
throw new Error(`unsupported gate image pin: ${String(fromGraph.gateImagePin)}`);
|
|
4307
|
+
}
|
|
4308
|
+
// Policy-selected Pod gates must pin their container to the resolved cut commit. Host sweeps do
|
|
4309
|
+
// not consume a container image pin, and the legacy no-executor path retains its old behavior.
|
|
4310
|
+
if (fromGraph.gateExecutor === 'pod' && fromGraph.gateImagePin !== 'cut-commit') {
|
|
4311
|
+
throw new Error('gateImagePin cut-commit is required for a policy-selected release gate');
|
|
4312
|
+
}
|
|
3951
4313
|
const baselineCommit = context?.outputs.cutoff?.baseline as { sha?: unknown } | undefined;
|
|
3952
4314
|
if (typeof baselineCommit?.sha === 'string') fromGraph.previousCommit = baselineCommit.sha;
|
|
3953
4315
|
if (fromGraph.gateExecutor !== undefined && !isGateExecutor(fromGraph.gateExecutor)) throw new Error(`gateExecutor must be pod or host: ${String(fromGraph.gateExecutor)}`);
|
|
@@ -3960,6 +4322,7 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
|
|
|
3960
4322
|
|| !['darwin', 'linux'].includes((value as { os?: string }).os ?? ''))))
|
|
3961
4323
|
throw new Error('gateShardCapabilities must map pod-N to {os: darwin|linux}');
|
|
3962
4324
|
const opts: GateOptions = {
|
|
4325
|
+
...(fromGraph.gateImagePin === 'cut-commit' ? { imagePin: 'cut-commit' as const } : {}),
|
|
3963
4326
|
rehearsalTui: fromGraph.rehearsalTui === true,
|
|
3964
4327
|
recheckFileLimit: fromGraph.gateRecheckFileLimit !== undefined ? Number(fromGraph.gateRecheckFileLimit) : undefined,
|
|
3965
4328
|
commit: typeof fromGraph.commit === 'string' ? fromGraph.commit : '',
|
|
@@ -3988,12 +4351,20 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
|
|
|
3988
4351
|
};
|
|
3989
4352
|
if (isGateExecutor(fromGraph.gateExecutor)) opts.executor = fromGraph.gateExecutor;
|
|
3990
4353
|
let freshShards = false;
|
|
4354
|
+
let warmPrecutCanaryFile: string | undefined;
|
|
3991
4355
|
let rerouteShard: string | undefined;
|
|
3992
4356
|
let shardHost: string | undefined;
|
|
3993
4357
|
for (let i = 0; i < args.length; i++) {
|
|
3994
4358
|
const arg = args[i];
|
|
3995
4359
|
if (arg === '--help') return 'help';
|
|
3996
4360
|
if (arg === '--json') continue;
|
|
4361
|
+
if (arg === '--precut-canary-file') {
|
|
4362
|
+
const value = args[++i];
|
|
4363
|
+
if (!value || value.startsWith('--')) throw new Error('--precut-canary-file requires one test file');
|
|
4364
|
+
warmPrecutCanaryFile = value;
|
|
4365
|
+
continue;
|
|
4366
|
+
}
|
|
4367
|
+
if (arg === '--full') { opts.full = true; continue; }
|
|
3997
4368
|
if (arg === '--rehearsal-baseline') { opts.rehearsalBaseline = true; continue; }
|
|
3998
4369
|
// 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).
|
|
3999
4370
|
if (arg === '--fresh-shards') { freshShards = true; continue; }
|
|
@@ -4037,6 +4408,7 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
|
|
|
4037
4408
|
else if (key === 'remoteMirror') opts.remoteMirror = value;
|
|
4038
4409
|
}
|
|
4039
4410
|
if (freshShards && opts.pod) opts.pod = { ...opts.pod, freshShards: true };
|
|
4411
|
+
if (warmPrecutCanaryFile) opts.warmPrecutCanary = { file: warmPrecutCanaryFile };
|
|
4040
4412
|
if ((rerouteShard || shardHost) && !opts.pod?.pool) throw new Error('shard reroute requires a Pod pool');
|
|
4041
4413
|
if (shardHost && !rerouteShard) throw new Error('--shard-host requires --reroute-shard');
|
|
4042
4414
|
if (opts.pod && rerouteShard) opts.pod = { ...opts.pod, rerouteShard, ...(shardHost ? { shardHost } : {}) };
|
|
@@ -4072,12 +4444,26 @@ export async function runGateNode(argv: string[], env: NodeJS.ProcessEnv, runner
|
|
|
4072
4444
|
catch (error) { debug.log('release-loop.gate', 'warm-profile-unavailable', { error: String(error) }); }
|
|
4073
4445
|
}
|
|
4074
4446
|
const opts = parsed === 'help' ? parsed : withWarmGateHooks(parsed, config, !!runner);
|
|
4447
|
+
if (opts !== 'help' && opts.warmPrecutCanary) {
|
|
4448
|
+
const canaryRunner = runner ?? createGateRunner(opts.repo ?? process.cwd(), opts.remote);
|
|
4449
|
+
const warmPrecutTestSeam = (runner as (GateRunner & { warmPrecutCanaryTestSeam?: {
|
|
4450
|
+
config: unknown; hooks: Pick<NonNullable<GateOptions['pod']>, 'warmReplay' | 'warmFinalize' | 'warmQuarantine' | 'warmReuseConfig'>;
|
|
4451
|
+
} }) | undefined)?.warmPrecutCanaryTestSeam;
|
|
4452
|
+
const canaryOpts = warmPrecutTestSeam && opts.pod ? { ...opts, pod: { ...opts.pod, ...warmPrecutTestSeam.hooks } } : opts;
|
|
4453
|
+
const canary = await runWarmPrecutCanary({ opts: canaryOpts, runner: canaryRunner, file: opts.warmPrecutCanary.file,
|
|
4454
|
+
config: warmPrecutTestSeam?.config ?? config,
|
|
4455
|
+
historyPath: verdictFile(opts.ledgerRoot ?? releaseLedgerRoot(), opts.version) });
|
|
4456
|
+
const output = { version: opts.version, commit: opts.commit, samples: canary.samples, decision: canary.decision };
|
|
4457
|
+
console.log(JSON.stringify(output));
|
|
4458
|
+
return 0;
|
|
4459
|
+
}
|
|
4075
4460
|
if (opts !== 'help') {
|
|
4076
4461
|
placement = gateExecutorOf(opts);
|
|
4077
|
-
debug.log('release-loop.gate', 'executor', { version: opts.version, ...placement, requested: opts.executor ?? null
|
|
4462
|
+
debug.log('release-loop.gate', 'executor', { version: opts.version, ...placement, requested: opts.executor ?? null,
|
|
4463
|
+
imagePin: opts.imagePin ?? null, imageCommit: opts.imagePin === 'cut-commit' ? opts.commit : null });
|
|
4078
4464
|
}
|
|
4079
4465
|
if (opts === 'help') {
|
|
4080
|
-
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.');
|
|
4466
|
+
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>] [--precut-canary-file <test-file>] [--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]\n--precut-canary-file runs exactly two one-file warm↔fresh comparisons and stores them in this version\'s gate-verdict warmHistory before the standard gate consumes it; decideWarmReuse applies minMatchSamples.\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.');
|
|
4081
4467
|
return 0;
|
|
4082
4468
|
}
|
|
4083
4469
|
version = opts.version;
|
|
@@ -4113,6 +4499,18 @@ export async function runGateNode(argv: string[], env: NodeJS.ProcessEnv, runner
|
|
|
4113
4499
|
return decision;
|
|
4114
4500
|
});
|
|
4115
4501
|
result.autoheal = result.autoheals.find((decision) => decision.action === 'hitl') ?? result.autoheals[0];
|
|
4502
|
+
} else if (versionPattern.test(version)) {
|
|
4503
|
+
// Some failures (for example an unreachable gate host or a measured regression) have no
|
|
4504
|
+
// stalled shard. Keep them in the same durable policy ledger instead of silently skipping autoheal.
|
|
4505
|
+
const cause = gateAutohealCause(result);
|
|
4506
|
+
if (cause) {
|
|
4507
|
+
const signature = createHash('sha256').update(JSON.stringify({ cause,
|
|
4508
|
+
error: result.error ?? null, introduced: [...(result.introduced ?? [])].sort() })).digest('hex');
|
|
4509
|
+
result.autoheal = decideAndRecordGateAutoheal({ cause, signature, version, runId: graphRunId ?? 'direct',
|
|
4510
|
+
shardKey: 'gate', shardKeys: ['gate'], shardElapsedMs: result.durationMs, overallElapsedMs: result.durationMs,
|
|
4511
|
+
storeRoot: autohealStoreRoot || releaseLedgerRoot() });
|
|
4512
|
+
debug.log('release-loop.gate-autoheal', 'decision', { runId: graphRunId, version, ...result.autoheal });
|
|
4513
|
+
}
|
|
4116
4514
|
}
|
|
4117
4515
|
if (result.error) console.error(result.error);
|
|
4118
4516
|
debug.log('release-loop.gate', 'result', { version, outcome: result.outcome, ...(placement ?? {}) });
|
|
@@ -4124,5 +4522,6 @@ if (import.meta.main) {
|
|
|
4124
4522
|
// The graph spawns this file as its own process, which inherits no daemon sink — without this every
|
|
4125
4523
|
// `release-loop.gate`/`release.gate`/`pod.pool` event stayed in the file trail and `elanous logs` read 0 rows.
|
|
4126
4524
|
await registerStandaloneLogSink('release-loop');
|
|
4127
|
-
process.exitCode = await runWithReleaseGateTraffic(process.env,
|
|
4525
|
+
process.exitCode = await runWithReleaseGateTraffic(process.env,
|
|
4526
|
+
() => runWithReleasePwaTraffic(process.env, () => runGateNode(process.argv.slice(2), process.env)));
|
|
4128
4527
|
}
|