elanous 0.2.42 → 0.2.44
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/{X1ucLw10Cds_h614NoEnh → HMXsxdy5MIIcHPMHv1wD4}/_buildManifest.js +1 -1
- package/apps/pwa/out/_next/static/chunks/08334415.f0e80896acbef722.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1032-78ba4f6391258cab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1082-53f86a26e03d309d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1109-7d0e264f6f2edbc7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1124-7c26330402481143.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2160.a097e7b138ae1c55.js → 118.2a89982fe4276880.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1244.6c435dbf1bf21d8a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1252.9e665904d3aa3b1a.js +32 -0
- package/apps/pwa/out/_next/static/chunks/1266.a46cc33cb6a72d75.js +104 -0
- package/apps/pwa/out/_next/static/chunks/1306.967240ede9a0f757.js +24 -0
- package/apps/pwa/out/_next/static/chunks/1344.3b95f3bd1b77a3fe.js +94 -0
- package/apps/pwa/out/_next/static/chunks/{4043.6b91203436c9c86b.js → 1401.22dfe92640a2b1ac.js} +4 -4
- package/apps/pwa/out/_next/static/chunks/1431-f7c78bd5be21e011.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1451-cdd35a8a0b31834b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6632.0eac1c554f8f2ef3.js → 1694.18debfd77562e56d.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/1708-f03d587b7a7545c8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1775-fa7152bdeff6c95f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/178-4da509cd7266672a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1850-522de4ee34957210.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1876-c6f6e0ddcc76c6a1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1976.097066b072432242.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3507.97e1977bc4d5fb0a.js → 1978.eeb60a606dc5deac.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/2043.e233e3b5a3e790f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2150.71b94e3348107c83.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2158.ebc6fc5a10280ab7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2216.802962c04f40a26e.js +64 -0
- package/apps/pwa/out/_next/static/chunks/2294.7890edcfa046c97d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/235.c89aaae4bcd6233a.js +74 -0
- package/apps/pwa/out/_next/static/chunks/{5885.0a3e52c8faccec98.js → 239.de075467b5523469.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/249.ba7051590d603c8f.js +226 -0
- package/apps/pwa/out/_next/static/chunks/2571.a6ac4e26c8618403.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2610.e149cd564a08913e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2705.9816f3c244f8b85f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2801.afeb8a4dae22f85f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2954.bbefc95d6b20e0d8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2e0f720a.a0697c08e3806a60.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3042.96ec235fe41c8d4e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3097-8e358f158acef46b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3205-93b1abe28b4fb16a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3273.b4434f0dcae7eb29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3311.9cbc43ea0c90d60c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1487.2f67b79444062da6.js → 3349.b5f09ee474d9533e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3503.a47a9755fb4d97b0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3506-5f429b3b7e1392b0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3843.bad404d008893291.js +77 -0
- package/apps/pwa/out/_next/static/chunks/3877-4b40e530b47e3bf9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/435-ce2958253cf7de61.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4393.28fa5228d0eec217.js +149 -0
- package/apps/pwa/out/_next/static/chunks/4404.7281c847c6e10dfc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8673-bab689a55f7e13b9.js → 4415-b9d6a90960920a4a.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/447-5b304de1fe098f43.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4484-8ebea83d431e6fae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8262.ddc03e06b733f184.js → 4502.bdcc877abd437a05.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4599-f95790fc5c65b8df.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1262.e804feb8fd1acb8c.js → 46.57c783fa4884714f.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/463-b9114e411f33728e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4659.a57d1f0c5d754cd2.js +36 -0
- package/apps/pwa/out/_next/static/chunks/4722-a847e7a28115035f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1063.2fb8a9dc55c731f2.js → 4757.67c6d7473a038e78.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4874-c86f878ec4502ad3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6377.bc7df239edbff244.js → 4891.3f80ba517a3b2373.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4998.810ca8833084caa9.js +62 -0
- package/apps/pwa/out/_next/static/chunks/4f98febc.ea5841d4d616bfb2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3482.a9ae9d2ad6a5bd97.js → 5052.7dbb12a55d4f18f5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5166-ca73b3e361085a6d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5180-039f4a18b07ad387.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5232.5a2ce4daf7a9e11d.js +115 -0
- package/apps/pwa/out/_next/static/chunks/5439.3fec6dbce29ecb36.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5612-074d9a5e00dfac6a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5675-01989d524a84c505.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5751.af641a1adf5ec697.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5758-c6cb98714d18ab0c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/578eded8.b582637306e16964.js +122 -0
- package/apps/pwa/out/_next/static/chunks/5790.5eb06a653ebd8571.js +34 -0
- package/apps/pwa/out/_next/static/chunks/5853-4033e5bd5afbe06b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5904.97d70bb7f46b2db7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3347.f846266b5e5f3ede.js → 5989.7ecd362c5c44b747.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/{73d2eb0e.4966ad55293c801a.js → 5a34fb2f.52f1b4e1baebb48a.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/{7a98bc80.fa935989e829f9c2.js → 5a5078f8.2099200e5ea95e16.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6036-c302c34ff0b3d410.js +1 -0
- package/apps/pwa/out/_next/static/chunks/606.ef81c313381f4264.js +1 -0
- package/apps/pwa/out/_next/static/chunks/616.1af54a928b1ba704.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6201.bc936f71f613a8e9.js +41 -0
- package/apps/pwa/out/_next/static/chunks/6204-fdc86e0e64c4f92d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7411.9b4b8b747dc7b802.js → 6245.1182e007e9d230d9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{b450776b.3db34d8d965f82d3.js → 63699a35.17262e162bc514b2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6420.494801a80030160c.js +27 -0
- package/apps/pwa/out/_next/static/chunks/{e8543907.5b040a5f22a0b0e5.js → 6549dad0.2740b25e51bf8f05.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{9427.96aa912ddb5801e0.js → 6881.9e7955847640c7fe.js} +17 -17
- package/apps/pwa/out/_next/static/chunks/6931.fcdfb94141e67b3d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6991-8e081c6ce38c0ba1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7010.1bbd17a7f2b3e376.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7029.d1466acf55fae2ed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7152-a123aa8e129c131f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7455.196168fffac3b9c4.js +24 -0
- package/apps/pwa/out/_next/static/chunks/7638.6ca4770dd1129138.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7649-f37d76260e5e2a5a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7679.32c40cdd14f20b4a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7751.04bfbd16b4c6cd0a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7864.b1ff31d4030b8994.js +148 -0
- package/apps/pwa/out/_next/static/chunks/7869-c47bd2c075c1fefa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8138.6c10b2a356d52fca.js → 8442.2f09ca969d631615.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8445.2738dbd59a78c78c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8447.5510c439f9938c6d.js +286 -0
- package/apps/pwa/out/_next/static/chunks/{9415.69e4a21c60c7d2db.js → 8551.f762945c2cda2e95.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8635-57a9d8d9db70f4f1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/86961958-4b0689a5048f756b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8709.3c6019bb18f8554f.js +8 -0
- package/apps/pwa/out/_next/static/chunks/8759.c999528a0a907bf6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9a852a62-6192a6ea9346ced3.js → 87f498e5-0881de09f2e44707.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8843.c3197f904c952b81.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3d0154af.5331fec737ffe8c3.js → 892fd990.456232f326cc39c2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8965-135a2c9eaa2464cb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8971-8d3b9c96f8a6c958.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9059.c8598d96f8e5e9cd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9115.90447ae198b84eae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9147.d9b0a02e0babc9b4.js +93 -0
- package/apps/pwa/out/_next/static/chunks/9204-83075f4b70c61cdc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9244-c32ef14b5dc72bdf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/930.2a9a30f964350c66.js +90 -0
- package/apps/pwa/out/_next/static/chunks/9398.1f56e3f17795f43f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9527.3e5ad31c891c5fcb.js +79 -0
- package/apps/pwa/out/_next/static/chunks/9571-4ac933e774d1225c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-9fc644de2d8edae3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-0c9dace2db62bee8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f5e65c3dc9d89f2a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-2a9c19b9a140b7b1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-61533ad92141adfb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-112004ced6fd0329.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-f733318c237a49d8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-51ea677d2d33bbc0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-428b41f2f9566e3c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-1b29396ea90a61e5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-cf83ec613ac904bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-3f4c0ecbc284bb5f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-b63c322f54f70cc6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-fe328c4ee9aac6b4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-bfb855c584f0fb98.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-9fc644de2d8edae3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-47b1710c110eabd7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-42895214458bc6d2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-d03de0ebd8366e23.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-3ed6c47e4b1c61c2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-ae653e9bf261c738.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-23f3f0a0209c0ac4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-88c691273aae66f9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-e187ac52a95538b3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-6e45828daed1295e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-edb5d7287bb46186.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-4a7eaacd93c3f6a5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-3ca073d43d74cc84.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-1e9fee67b1199436.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-df1283ea963c6816.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-19fcf8d8301ccd18.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-5ab699e0303bbb40.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-7cde0d29756984c5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-9d4c598e8ee22f7a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-cc1609a71f44ec62.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-7659a9a1345bfaa9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-0afef60ecc0c4af5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-a3553949f8f34f5f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-9fc644de2d8edae3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-057a69702f3c67c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-c0f0e570f3f74c73.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-1825071ae34e3b89.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-6244e3e135d8ccfc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-da787e8a10b8a82e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-1316d32498e2be42.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-4df94572e343de2e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-708969f7475e88c1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-c58add08c919a1dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-a24847f45e5d5bf9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-acfec65a3a60e22e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-3dfc598e6fbde245.js +1 -0
- package/apps/pwa/out/_next/static/chunks/framework-c658a626118411b7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-4c73685fd891445b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-6310f7e5de7dd01d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-51c326bf48610be1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-d0beadf754da59b0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-acd243bfbf3f6732.js +1 -0
- 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 +23 -23
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +23 -23
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +23 -23
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +22 -22
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +22 -22
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +23 -23
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +23 -23
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +23 -23
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +22 -22
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +22 -22
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +22 -22
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +23 -23
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +23 -23
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +22 -22
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +22 -22
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +22 -22
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +22 -22
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +23 -23
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +23 -23
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +22 -22
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +23 -23
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +23 -23
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +23 -23
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +23 -23
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +23 -23
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +22 -22
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +23 -23
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +23 -23
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +23 -23
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +23 -23
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +23 -23
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +23 -23
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +23 -23
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +23 -23
- package/catalog/external-commands.yaml +5 -3
- package/catalog/meta/catalogVersion.yaml +2 -1
- package/catalog/signing/catalog-signing-public.pem +3 -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/README.md +6 -0
- package/graphs/overlays/examples/research-runtime-empty-terminal.yaml +28 -0
- package/graphs/profiles/local.yaml +5 -0
- package/graphs/profiles/pod.yaml +4 -0
- package/graphs/release/release-loop.yaml +33 -17
- package/graphs/units/verbs/make-doc.yaml +1 -0
- 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/bench-metering-plan.ts +229 -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-host.ts +56 -0
- package/scripts/demo/demo1104-parallel-scenes.ts +301 -0
- package/scripts/demo/demo1104-parallel.ts +72 -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/hooks/codex-notify.sh +32 -0
- package/scripts/install.sh +10 -38
- package/scripts/landing-heal/land-plan.ts +57 -0
- package/scripts/public-leak-baseline.txt +0 -1
- package/scripts/release-loop/auto-approve-node.ts +1 -1
- package/scripts/release-loop/docs-node.ts +4 -2
- package/scripts/release-loop/gate-autoheal-sim-evidence.ts +60 -0
- package/scripts/release-loop/gate-image-pin.ts +116 -2
- package/scripts/release-loop/gate-method.ts +139 -0
- package/scripts/release-loop/gate-node.ts +395 -43
- package/scripts/release-loop/gate-progress-sim.ts +73 -0
- package/scripts/release-loop/gate-traffic-mode.ts +151 -3
- package/scripts/release-loop/node-verdict.ts +38 -25
- package/scripts/release-loop/notes-repair.ts +18 -1
- package/scripts/release-loop/preflight.ts +544 -10
- package/scripts/release-loop/render-warm-control-manifest.ts +22 -0
- package/scripts/release-loop/resume-release.ts +9 -7
- package/scripts/release-loop/resume.ts +13 -4
- package/scripts/release-loop/unattended-release.ts +20 -9
- package/scripts/release-loop/warm-control-forward.ts +178 -0
- package/scripts/retro-cycle.ts +30 -8
- package/scripts/rhythm/daily.ts +39 -4
- package/scripts/rhythm/linear-progress-sync.ts +311 -0
- package/scripts/ux-sim.ts +7 -1
- package/scripts/video-pipeline.ts +9 -1
- package/scripts/warm-pool-provider-config.ts +35 -7
- 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 +17 -12
- package/src/acp/event-router.ts +20 -1
- package/src/acp/server.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 +107 -0
- package/src/agent-mission/hand-hooks.ts +101 -0
- package/src/agent-mission/mission-cli.ts +56 -21
- 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 +256 -18
- package/src/agent-mission/tool-install.ts +122 -11
- package/src/agent-substrate/pr-reviewer.ts +147 -11
- package/src/agent-substrate/self-review-cli.ts +10 -2
- package/src/agents/general-purpose.md +1 -1
- package/src/auto-research/research-fanout.ts +221 -0
- package/src/auto-research/research-turn.ts +362 -31
- package/src/autopilot/mission-codebase-gate.ts +7 -3
- package/src/autopilot/pr-manager.ts +31 -3
- 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/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/control-cli.ts +15 -6
- package/src/cli/doctor-fix.ts +2 -2
- package/src/cli/intake-source-cli.ts +44 -8
- package/src/cli/log-event-names.ts +8 -0
- package/src/cli/msg-cli.ts +32 -3
- package/src/cli/pr-cli.ts +21 -1
- package/src/cli/pr-land-heavy-remote.ts +44 -6
- package/src/cli/pr-land-overlap-evidence.ts +13 -1
- package/src/cli/pr-land-pod-shadow-job.ts +143 -5
- package/src/cli/pr-land-pod-shadow.ts +9 -2
- package/src/cli/release-checklist-read-cli.ts +44 -16
- package/src/cli/release-cli.ts +23 -3
- package/src/cli/research-cli.ts +9 -0
- package/src/cli/setup-cli.ts +166 -6
- package/src/cli/setup-keys.ts +199 -0
- package/src/cli/task-plugin-wizard-host.ts +114 -0
- package/src/cli/tasks-cli.ts +54 -9
- package/src/cli/tui-test-cli.ts +190 -0
- 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 +42 -4
- package/src/control-plane/member-tokens.ts +25 -2
- package/src/control-plane/ports.ts +8 -3
- package/src/control-plane/server.ts +26 -5
- package/src/core-turn/deep-turn-bloom.ts +67 -0
- package/src/core-turn/research-policy.ts +43 -0
- package/src/core-turn/run-core-turn.ts +488 -51
- 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 +192 -0
- package/src/dashboard/classic-sidetab-mission-pair.ts +82 -0
- package/src/dashboard/classic-sidetab-mission-trio.ts +242 -0
- package/src/dashboard/classic-sidetab-mission.ts +169 -5
- package/src/dashboard/index.ts +387 -14
- package/src/dashboard/input/chat-main-acp-dispatch.ts +23 -0
- package/src/dashboard/input/chat-main-plain-turn-runtime.ts +8 -0
- package/src/dashboard/mission-cli-consumer.ts +20 -8
- package/src/dashboard/plugin-wizard-runtime.ts +50 -0
- package/src/dashboard/plugin-wizard-tool.ts +55 -0
- package/src/dashboard/sidetab-runtime.ts +3 -1
- package/src/dashboard/terminal-popup-runtime.ts +6 -3
- package/src/dashboard/visible-mission-diagnostics.ts +16 -0
- 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/retro-cycle-observation.ts +43 -0
- package/src/domains/retro-cycle.ts +13 -2
- package/src/domains/retro-proposals.ts +92 -0
- package/src/domains/retro-report.ts +6 -1
- 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 +24 -1
- package/src/graph-runner/graph-grow-llm.ts +3 -1
- package/src/graph-runner/graph-run-api.ts +3 -1
- package/src/graph-runner/runner.ts +310 -46
- package/src/graph-wizard/mission-compose.ts +407 -12
- package/src/harness/dispatch-backpressure.ts +54 -1
- package/src/harness/dispatch-shadow-compare.ts +74 -0
- package/src/harness/harness-cli-command.ts +15 -3
- package/src/harness/harness-pod-dispatch.ts +3 -0
- 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 +129 -12
- 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/child-first-llm-observation.ts +112 -0
- package/src/llm/provider-credentials.ts +68 -6
- package/src/llm/provider-failure-evidence.ts +44 -0
- package/src/llm.ts +172 -12
- package/src/maturity/feature-maturity.ts +3 -3
- package/src/mcp/client.ts +7 -0
- package/src/mcp/proxy-runtime.ts +6 -1
- package/src/mcp/server.ts +54 -0
- package/src/msg/msg-sse-server.ts +3 -3
- package/src/msg/msg-store.ts +177 -19
- package/src/native-tool-catalog.ts +36 -2
- package/src/nexus/api/consult-requests.ts +18 -6
- package/src/nexus/api/discord-trigger-bot.ts +6 -6
- package/src/nexus/api/graphs-api.ts +4 -2
- package/src/nexus/api/grokbot-trigger-bot.ts +206 -0
- package/src/nexus/api/http-server.ts +37 -1
- package/src/nexus/api/mcp-public-allowlist.ts +2 -0
- package/src/nexus/api/telegram-trigger-bot.ts +6 -6
- package/src/nexus/boot/register-mcp-clients.ts +114 -8
- package/src/nexus/index.ts +22 -2
- package/src/nexus/outbound/format.ts +9 -0
- package/src/nexus/outbound/grokbot-adapter.ts +71 -0
- package/src/nexus/outbound/router.ts +121 -16
- 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 +81 -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 +126 -29
- 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 +130 -0
- package/src/preview/terminal.ts +22 -7
- package/src/prompt-enhance/enhance.ts +10 -0
- package/src/pty-shell/pty-control-ipc.ts +4 -1
- package/src/pty-shell/pty-write-provenance.ts +21 -3
- package/src/pty-shell/registry.ts +9 -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/proposal-auto-place.ts +83 -0
- package/src/research/axis-fanout.ts +79 -29
- package/src/research/lenses/second-order.ts +312 -0
- package/src/self-dev/ask-launch-flow.ts +72 -1
- package/src/self-dev/dev-cli.ts +7 -2
- package/src/self-dev/dev-pipeline.ts +5 -0
- package/src/self-dev/draft-residue.ts +64 -5
- package/src/self-dev/draft-sweep.ts +31 -10
- package/src/self-dev/draft-triage-rules.ts +60 -3
- 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.ts +58 -0
- package/src/self-dev/orchestrate.ts +1 -1
- package/src/self-dev/run-supervisor.ts +152 -2
- 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 +30 -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 +549 -10
- package/src/self-implement/graph-overlay-yaml.ts +181 -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 +94 -5
- package/src/self-implement/off-diff-evidence.ts +249 -5
- package/src/self-implement/orchestrator.ts +298 -26
- package/src/self-implement/run-chart.ts +6 -3
- package/src/self-implement/run-ledger.ts +51 -1
- package/src/self-implement/runtime-overlay-run-steps.ts +95 -0
- package/src/self-implement/tdd-evidence-tags.ts +9 -0
- package/src/self-implement/test-scenario-request.ts +37 -6
- 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 +6 -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 +74 -6
- package/src/session-runtime/retry-policy.ts +1 -1
- 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 +205 -10
- package/src/sidetab-core/visible-agent-mission-pair.ts +51 -0
- package/src/sidetab-core/visible-agent-mission.ts +182 -48
- package/src/skills/tools/agent.ts +23 -4
- package/src/skills/tools/omni-search.ts +127 -27
- package/src/task-agent/parent-host.ts +29 -1
- package/src/task-agent/recall-inject.ts +98 -0
- package/src/task-agent/repair-outcomes.ts +65 -0
- package/src/task-agent/task-hand.ts +46 -14
- 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 +23 -1
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +107 -1
- package/src/task-orchestrator/surfaces/pod-ledger-events.ts +113 -14
- package/src/task-orchestrator/surfaces/pod-si-warm-host.ts +21 -9
- package/src/task-orchestrator/surfaces/pod-source-receive.ts +23 -11
- package/src/task-orchestrator/surfaces/pod-warm-switch.ts +2 -0
- package/src/task-orchestrator/surfaces/pod-warm-worker-pod.ts +1 -1
- package/src/task-orchestrator/surfaces/pod-warm-worker-provider.ts +467 -77
- package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +45 -3
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +210 -14
- 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/tool-hints/gate.ts +46 -14
- package/src/tool-hints/signals.ts +1 -1
- package/src/tool-hints/types.ts +2 -0
- package/src/tool-runtime/agent-runtime.ts +1 -0
- package/src/tool-runtime/browser-hand-runtime.ts +391 -0
- package/src/tool-runtime/browser-runtime.ts +33 -19
- package/src/tool-runtime/elanous-linear-issue-runtime.ts +76 -0
- package/src/tool-runtime/elanous-linear-secretary-runtime.ts +173 -0
- package/src/tool-runtime/graph-mcp-runtimes.ts +240 -0
- package/src/tool-runtime/index.ts +6 -0
- package/src/tool-runtime/types.ts +5 -0
- package/src/tui/entry.ts +51 -8
- package/src/tui/sidetab/attach.ts +252 -11
- package/src/tui/sidetab/herdr-portal.ts +225 -0
- package/src/tui/sidetab/list.ts +3 -2
- package/src/tui-client/next-dashboard-runtime.ts +52 -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 +199 -0
- package/src/ux-sim/fake-browser-page.ts +81 -0
- package/src/ux-sim/fake-linear-device.ts +31 -0
- package/src/ux-sim/graph-engine-sim.ts +47 -0
- package/src/ux-sim/linear-secretary.ts +208 -0
- package/src/ux-sim/mock-scenario-catalog.json +81 -26
- package/src/ux-sim/mock-scenario-catalog.ts +175 -5
- package/src/ux-sim/onboarding-first-run-device.ts +31 -21
- package/src/ux-sim/release-sim-rig.ts +62 -18
- package/src/ux-sim/scenario.ts +70 -26
- package/src/ux-sim/service-key-setup.ts +113 -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/web-search/exa.ts +77 -0
- 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/0244e5b5.6fc7670f8223d82d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1121.13ba7a8aff19df81.js +0 -36
- package/apps/pwa/out/_next/static/chunks/1147-5196d19c222051b3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1319.b9b815ae0f181e06.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1345-31427b833438f521.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1455-530ca16c7e71baff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1574.878c18ecc97b1bd4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1586.fafc33a9a5835cb7.js +0 -32
- package/apps/pwa/out/_next/static/chunks/1744.855fd60ae4c4a8c8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/193.21a47a3e07ef6860.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1945.7b54990d578c119b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2004.e839fda7ea5a738a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2044-dd681a5c08779ff8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2101.2248decb38829b03.js +0 -286
- package/apps/pwa/out/_next/static/chunks/2300.41ea70ac1bef1446.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2357-07b5eeb5fb12628d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2364.28652020695a6db3.js +0 -34
- package/apps/pwa/out/_next/static/chunks/2456.b6de0e1c05f28843.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2526.c3e50ac1fec064bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2549-cda50b716ff4250c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2550-5f1e88f615568045.js +0 -1
- package/apps/pwa/out/_next/static/chunks/261-319078c954e40d66.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2690.0521c738b7749931.js +0 -115
- package/apps/pwa/out/_next/static/chunks/2974.595d1e29d040cb6a.js +0 -64
- package/apps/pwa/out/_next/static/chunks/3207.a756c95e9ec8c3c8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3285.89ee2f298682f5e5.js +0 -93
- package/apps/pwa/out/_next/static/chunks/3447.06edc7b092021417.js +0 -41
- package/apps/pwa/out/_next/static/chunks/3510-9ca392698bfe4391.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3541.772e5ce425aaa611.js +0 -24
- package/apps/pwa/out/_next/static/chunks/3741.9cf76acaa2ee5298.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3837.a210d7d0db8d8abd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3898-955c896f394217a6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3947-ef57acea76eaa8d2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4037-3b82ebc7d78afdc7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/405.9022d4a478202b67.js +0 -77
- package/apps/pwa/out/_next/static/chunks/417-fb2cc9a9910e5968.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4276.336d76d858fcdf1b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4302.6ced9aa4535de947.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4320.583cbab64db6be02.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4768-8eac183fd291ca53.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4770.769c9cd9c7034eba.js +0 -148
- package/apps/pwa/out/_next/static/chunks/4895.65605ec3e6504e89.js +0 -74
- package/apps/pwa/out/_next/static/chunks/50061469.0326382241ef3b2d.js +0 -122
- package/apps/pwa/out/_next/static/chunks/5112.4b9f300124fab7b3.js +0 -149
- package/apps/pwa/out/_next/static/chunks/5335.375ee922bcd5bc83.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5342.023a2e729689b320.js +0 -27
- package/apps/pwa/out/_next/static/chunks/53c73f84.373b6160adfa89a4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5431-72a1dbde59400691.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5440-4236de26c5ea5856.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5486-b318c165f645d8ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5506-c7d2ec25c62a7c94.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5570-c66ecbea26b4e3b1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5696.ffca5aa259d8451e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5793-8c584aea2aa87560.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5848-6de6b8c173351a90.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5999.94e3c83040200f31.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5a7901d6-2d8d9b5587cf9042.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6131.348b7d6bdcd267ce.js +0 -226
- package/apps/pwa/out/_next/static/chunks/6164.86133a0172611184.js +0 -90
- package/apps/pwa/out/_next/static/chunks/6515.e07e8b2b8a707a87.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6522-c005514f87d18982.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6642.648adb1767f39ecf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6848.138805a09d001d09.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7049-c09e2b7a253a24f3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7072.1e00447d9fa39972.js +0 -104
- package/apps/pwa/out/_next/static/chunks/7106.64201c539c5e220c.js +0 -94
- package/apps/pwa/out/_next/static/chunks/7151-1f742b455986530e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7199.18d6b25e001154ce.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7279-c199ea83f34338fa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/728b938d.ffaa7ad583d60e58.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7309.171c3de54e0322f1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/733.7a1ab3342e03306f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7504.d5cf1e7cd89b1b08.js +0 -24
- package/apps/pwa/out/_next/static/chunks/7573-0fd4af62d65d4598.js +0 -1
- package/apps/pwa/out/_next/static/chunks/759.6f8267df4689c401.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7825-668b046f72f14483.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7852-1538768b6eb19388.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7868.cff810430bb63c91.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8084-dfcc796874ab4e07.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8266.6bf9b131b2584678.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8403.7b81d5e64074a725.js +0 -8
- package/apps/pwa/out/_next/static/chunks/8470.1998cbd0866a7eac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8505.fd803c6c33b54cc3.js +0 -79
- package/apps/pwa/out/_next/static/chunks/8546-6f03f8ea34dd61d9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8687-246fda8433d3477c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/898-280022fb082936bc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9098-50eefc1844212e3d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9211-330b6736a63d4cb3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9249.976f1e69a9d80754.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9255-cf88fceda5666645.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9306-e523ba58bb8adc42.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9324.a9866d717ba62cb6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9325.a8510e8aa87c8143.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9353-12bfff0c130104cf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9479-154a697d4b6e4e98.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9540-65aa6ccc31b3fd78.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9633.ce5bddc56784da72.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9656-374232558fd528d0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9832.f4a98c878ecf2c0a.js +0 -62
- package/apps/pwa/out/_next/static/chunks/9865.f094440645f02d98.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-e772b4710df5d936.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-4361368e6a76b967.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-802f349fdd65248d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-7f35513988a2a495.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-798121656540e33e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-20a089490affe98c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-e5e4ec786d70e604.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-d12fe5d0801430e4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-d8cf21022fecc026.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-21ce565c9c57ee97.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-b56563025f619c9b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-52f14dd133da8348.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-c0ed7fdcdba76633.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-af8c3020cb520f15.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-72eda67af9f5e35c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-e772b4710df5d936.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-67a6620d31f0a46f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-f059363aa0880ec2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-f9f917d6a57f69fb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-d3ae55eee2f0826c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-0e3d7b9421b79dac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-ba738f7e233eb211.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-33438b98c74fd599.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-0b34d5fd990d793a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-0f291d3c8fd55a3a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-3fced192be5279c1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-04cb3cf182a7c1ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-6ccbd2ab23a21542.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-aa0518c48a5f4e56.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-4b32226d341ad7d7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-3224fd8f6383b61f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-efbc97459f2914e2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-2fe487d88cd5d97c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-48861dd1a858bab7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-197d5d4d21d228ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a763ba89fbdd9c27.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-7edb253510b1c136.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-0c875702c61965ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-e772b4710df5d936.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-e425c951151095f7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-9c90e0aec4e27f77.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-733e09a02e727d42.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-4f899e14c34d77a1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-bf067887b3307423.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-7af10868ff87d04e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-efe5304057aa11c7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-76bcd29aaa8a26c3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-4cfd88e2273ce50f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-0a1ddb47efba0a32.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2a176c707152c892.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-efe03031b8797fbf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-d0d2d673bfd8a8a9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-4c9fceb2baa09cb7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-b0ab8d8fc9a97556.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-7e2cc62a2e5cec96.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-22324935f753ec5e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-9dc1bfa25334998a.js +0 -1
- package/apps/pwa/out/_next/static/css/be9019f696217521.css +0 -1
- /package/apps/pwa/out/_next/static/{X1ucLw10Cds_h614NoEnh → HMXsxdy5MIIcHPMHv1wD4}/_ssgManifest.js +0 -0
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
// ⛔ 읽기 전용 — 칸을 옮기지도, 동결을 바꾸지도, push 하지도 않는다. 옮길 것은 «명령»으로 제안만 한다.
|
|
6
6
|
// ⛔ 게이트 규칙을 다시 쓰지 않는다 — checklist-gate 노드 · release run 진입 검사 · 예약 시작 준비 검사(releaseReadiness, #24557)가
|
|
7
7
|
// 함께 쓰는 단 하나의 판정 cutChecklistGate 를 그대로 부르고, 예약 시작 경로는 releaseReadiness 를 CLI 와 같은 인자로 부른다.
|
|
8
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, mkdtempSync } from 'node:fs';
|
|
9
|
-
import { join } from 'node:path';
|
|
8
|
+
import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, mkdtempSync } from 'node:fs';
|
|
9
|
+
import { dirname, join } from 'node:path';
|
|
10
10
|
import { tmpdir } from 'node:os';
|
|
11
11
|
import { spawnSync } from 'node:child_process';
|
|
12
12
|
import { cutChecklistGate, listChecklist, type ChecklistItem } from '../../src/release-loop/checklist.js';
|
|
@@ -16,10 +16,13 @@ import { effectiveInstanceRoot } from '../../src/instance/resolve.js';
|
|
|
16
16
|
import { debug } from '../../src/debug/log.js';
|
|
17
17
|
import { releaseReadiness } from './release-readiness.js';
|
|
18
18
|
import { latestPublishedPreviousVersion, runUnattendedRelease, type UnattendedReleaseDeps } from './unattended-release.js';
|
|
19
|
-
import { loadExportConfig, run as runPublicExport, selectExportFiles } from '../public-export.js';
|
|
19
|
+
import { gitIgnoredExportFiles, loadExportConfig, run as runPublicExport, selectExportFiles } from '../public-export.js';
|
|
20
|
+
import { SPAWN_SYNC_MAX_BUFFER } from '../../src/util/spawn-sync-output.js';
|
|
21
|
+
import { NODE_REGISTRY, parsePodPool } from '../../src/task-orchestrator/surfaces/pod-pool.js';
|
|
20
22
|
|
|
21
23
|
export type PreflightLevel = 'ok' | 'warn' | 'block';
|
|
22
|
-
export type PreflightCheck = 'checklist' | 'postpub' | 'run-input' | 'freeze' | 'schedule' | 'export'
|
|
24
|
+
export type PreflightCheck = 'checklist' | 'postpub' | 'run-input' | 'freeze' | 'schedule' | 'export' | 'notes' | 'cut-tree' | 'gate-slot' | 'release-notes'
|
|
25
|
+
| 'cutoff-notes' | 'gitignored-export' | 'pwa-build' | 'gate-image';
|
|
23
26
|
export interface PreflightFinding { level: PreflightLevel; check: PreflightCheck; id?: string; message: string; suggestion?: string }
|
|
24
27
|
export interface GateMeasure { version: string; runId: string; gateSeconds: number; toPublishSeconds?: number }
|
|
25
28
|
const compareStable = (a: string, b: string) => { const x = a.split('.').map(Number), y = b.split('.').map(Number); return x[0]! - y[0]! || x[1]! - y[1]! || x[2]! - y[2]!; };
|
|
@@ -32,6 +35,14 @@ export interface PreflightResult {
|
|
|
32
35
|
findings: PreflightFinding[];
|
|
33
36
|
schedule: { cutAt: string; landBy: string | null; publishAt: string | null; publishAtSource: 'option' | 'schedule' | 'cadence' | null; exportCheckAt: string } | null;
|
|
34
37
|
exportCheck: { mainSha: string; files: number; leaks: number; codeLiterals: number } | null;
|
|
38
|
+
cutoffExportCheck: { commit: string; gitIgnoredLeaks: number | null; error?: string } | null;
|
|
39
|
+
/** Read-only T−120 evidence. It describes checks only; it never means a release stage ran. */
|
|
40
|
+
countdownReadiness: {
|
|
41
|
+
status: 'green' | 'red' | 'unmeasured';
|
|
42
|
+
notes: { status: 'green' | 'red' | 'unmeasured'; addedLines: number | null };
|
|
43
|
+
export: { status: 'green' | 'red' | 'unmeasured'; leaks: number | null };
|
|
44
|
+
checklist: { status: 'green' | 'red' | 'unmeasured' };
|
|
45
|
+
};
|
|
35
46
|
/** The immediately previous release (by version order) whose gate is compared. */
|
|
36
47
|
previousVersion: string | null;
|
|
37
48
|
gateMeasure: GateMeasure | null;
|
|
@@ -39,8 +50,76 @@ export interface PreflightResult {
|
|
|
39
50
|
fallbackGateMeasure: GateMeasure | null;
|
|
40
51
|
}
|
|
41
52
|
|
|
53
|
+
// ── RELEASE-COUNTDOWN-SEQUENCE — T−120 → T0 발사 시퀀스 ─────────────────────────────────────────
|
|
54
|
+
// 누리호 발사 체계의 사상: H0 전 자동 시퀀스 · 단계별 녹색/빨강 한 화면 · 범위 이탈(빨강)이면 자동 중지 ·
|
|
55
|
+
// 최후 수동 취소 구간 · 실패는 «부적합 보고»로 원장에 남겨 다음 판에 반영한다.
|
|
56
|
+
// 단계 → 검사 묶음은 기존 findings 의 check 분류를 «그대로» 재사용한다(게이트 규칙을 다시 쓰지 않는다).
|
|
57
|
+
export type CountdownStage = 'T-120' | 'T-60' | 'T-30' | 'T0';
|
|
58
|
+
|
|
59
|
+
/** What `scripts/release-loop/cutoff.ts` pins at T−60 (`<instance>/release/<version>/manifest.json`). */
|
|
60
|
+
export interface CountdownManifest {
|
|
61
|
+
cutoff?: { sha?: string };
|
|
62
|
+
fragments?: { byPr: number; byNextMd: number; unlinked: number; unknown: number };
|
|
63
|
+
escalate?: Array<{ kind?: string }>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The «node-b» gate slot: the dispatcher is paused and the pod target lowered so the gate can take a Pod. */
|
|
67
|
+
export interface CountdownGateSlot { dispatcherPaused?: boolean; podTargetFrom?: number; podTargetTo?: number }
|
|
68
|
+
|
|
69
|
+
export interface CountdownStep {
|
|
70
|
+
stage: CountdownStage;
|
|
71
|
+
/** Minutes before the cut. */
|
|
72
|
+
offsetMinutes: number;
|
|
73
|
+
/** Cut − offset (ISO); null when the release has no stored schedule. */
|
|
74
|
+
at: string | null;
|
|
75
|
+
label: string;
|
|
76
|
+
level: PreflightLevel;
|
|
77
|
+
findings: PreflightFinding[];
|
|
78
|
+
/** Not judged — an earlier step went red and the sequence stopped. */
|
|
79
|
+
skipped: boolean;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface NonconformanceEntry {
|
|
83
|
+
version: string;
|
|
84
|
+
stage: CountdownStage;
|
|
85
|
+
/** 원인 */
|
|
86
|
+
cause: string;
|
|
87
|
+
/** 수리 */
|
|
88
|
+
repair: string;
|
|
89
|
+
/** 다음 판 반영 */
|
|
90
|
+
nextRelease: string;
|
|
91
|
+
at: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface CountdownResult {
|
|
95
|
+
version: string;
|
|
96
|
+
next: string;
|
|
97
|
+
cutAt: string | null;
|
|
98
|
+
verdict: PreflightLevel;
|
|
99
|
+
steps: CountdownStep[];
|
|
100
|
+
/** The stage that went red and stopped the sequence. */
|
|
101
|
+
haltedAt: CountdownStage | null;
|
|
102
|
+
/** 담당 자리 우편 — sent when the sequence stops. */
|
|
103
|
+
mail: { from: string; to: string; subject: string; body: string } | null;
|
|
104
|
+
/** 최후 수동 취소 구간 — the last seconds before T0 in which a human can still abort. */
|
|
105
|
+
manualCancel: { windowStartAt: string; windowEndAt: string; totalResponsibleSeconds: number; ldSeconds: number } | null;
|
|
106
|
+
nonconformance: NonconformanceEntry[];
|
|
107
|
+
/** The target «부적합 보고» ledger (JSON Lines); dry-run does not append. */
|
|
108
|
+
nonconformanceLedger: string;
|
|
109
|
+
sideEffectsEnabled: boolean;
|
|
110
|
+
preflight: PreflightResult;
|
|
111
|
+
}
|
|
112
|
+
export type ReleaseCountdownStep = {
|
|
113
|
+
id: 'notes-and-readiness' | 'pinned-main-preflight' | 'gate-window' | 'cut';
|
|
114
|
+
offsetMinutes: number;
|
|
115
|
+
at: string;
|
|
116
|
+
action: string;
|
|
117
|
+
observation: 'not-observed-by-countdown';
|
|
118
|
+
};
|
|
119
|
+
|
|
42
120
|
export interface PreflightDeps {
|
|
43
121
|
now?: Date;
|
|
122
|
+
countdown?: boolean;
|
|
44
123
|
/** Planned publish time (ISO). Without it, the October cadence (07:00 · 18:00 KST) after the cut is assumed. */
|
|
45
124
|
publishAt?: string;
|
|
46
125
|
runInput?: (version: string) => Promise<{ input: unknown }>;
|
|
@@ -52,14 +131,40 @@ export interface PreflightDeps {
|
|
|
52
131
|
previousVersion?: string;
|
|
53
132
|
/** Injectable for deterministic preflight tests. Production pins origin/main and scans a disposable checkout. */
|
|
54
133
|
exportCheck?: () => Promise<{ mainSha: string; files: number; leaks: number; codeLiterals: number }>;
|
|
134
|
+
/** Release cut −60: build the pinned candidate PWA from a disposable checkout. */
|
|
135
|
+
pwaBuildCheck?: (commit: string) => Promise<{ ok: boolean; detail: string; commit?: string }>;
|
|
136
|
+
/** Release cut −60: count export-gitignored hits against the exact cutoff tree. */
|
|
137
|
+
gitIgnoredExportCheck?: (commit: string) => Promise<number | null>;
|
|
138
|
+
/** Release cut −60: prove the candidate's immutable commit tag exists in at least one configured gate pool. */
|
|
139
|
+
gateImageTagCheck?: (commit: string, poolSpec: string) => { checked: boolean; pools: string[]; detail?: string };
|
|
140
|
+
/** Countdown T−120/T−60/T0: the cut manifest `cutoff.ts` pinned (default `<instance>/release/<v>/manifest.json`). */
|
|
141
|
+
manifest?: (version: string) => CountdownManifest | null;
|
|
142
|
+
/** Countdown T−60: the «node-b» gate-slot record (default `<instance>/release/<v>/gate-slot.json`). */
|
|
143
|
+
gateSlot?: (version: string) => CountdownGateSlot | null;
|
|
144
|
+
/** Countdown seat mail — default posts `eln msg post --from OP --to <담당 자리>`. */
|
|
145
|
+
postMail?: (mail: { from: string; to: string; subject: string; body: string }) => void;
|
|
146
|
+
/** Countdown «부적합 보고» ledger (JSON Lines) — default `<instance>/release/nonconformance.jsonl`. */
|
|
147
|
+
nonconformanceLog?: string;
|
|
148
|
+
/** Injectable read-only comparison of release/next.md against the previous published tag. */
|
|
149
|
+
releaseNotesCheck?: (previousVersion: string | undefined, mainSha: string | undefined) => Promise<number>;
|
|
150
|
+
/** Explicitly enable owner mail and nonconformance ledger append; defaults to dry-run. */
|
|
151
|
+
allowSideEffects?: boolean;
|
|
55
152
|
}
|
|
56
153
|
|
|
57
154
|
function runGit(cwd: string, args: string[]): string {
|
|
58
|
-
const result = spawnSync('git', args, { cwd, encoding: 'utf8', maxBuffer: 16 << 20 });
|
|
155
|
+
const result = spawnSync('git', args, { cwd, encoding: 'utf8', timeout: 60_000, maxBuffer: 16 << 20 });
|
|
59
156
|
if (result.status !== 0) throw new Error(`git ${args.join(' ')} 실패 rc=${result.status}: ${(result.stderr ?? '').trim().slice(0, 300)}`);
|
|
60
157
|
return (result.stdout ?? '').trim();
|
|
61
158
|
}
|
|
62
159
|
|
|
160
|
+
/** Avoid partial-clone promisor remotes turning a missing fixture/cut SHA into an implicit network fetch. */
|
|
161
|
+
function hasLocalCommit(repoRoot: string, commit: string): boolean {
|
|
162
|
+
const result = spawnSync('git', ['cat-file', '-e', `${commit}^{commit}`], {
|
|
163
|
+
cwd: repoRoot, env: { ...process.env, GIT_NO_LAZY_FETCH: '1' }, encoding: 'utf8', timeout: 5_000, maxBuffer: SPAWN_SYNC_MAX_BUFFER,
|
|
164
|
+
});
|
|
165
|
+
return result.status === 0;
|
|
166
|
+
}
|
|
167
|
+
|
|
63
168
|
/** Read-only main snapshot: all checkout and cleanup writes are contained in a disposable temp directory. */
|
|
64
169
|
export async function scanOriginMainExport(): Promise<{ mainSha: string; files: number; leaks: number; codeLiterals: number }> {
|
|
65
170
|
const repoRoot = runGit(process.cwd(), ['rev-parse', '--show-toplevel']);
|
|
@@ -76,7 +181,7 @@ export async function scanOriginMainExport(): Promise<{ mainSha: string; files:
|
|
|
76
181
|
needed.add('release/public-export.yaml');
|
|
77
182
|
// Read only the paths the pinned manifest can inspect; the full tracked-path index stays at mainSha.
|
|
78
183
|
const manifestPath = join(snapshot, 'release/public-export.yaml');
|
|
79
|
-
const manifest = spawnSync('git', ['-C', snapshot, 'show', `${mainSha}:release/public-export.yaml`], { encoding: 'utf8', maxBuffer: 4 << 20 });
|
|
184
|
+
const manifest = spawnSync('git', ['-C', snapshot, 'show', `${mainSha}:release/public-export.yaml`], { encoding: 'utf8', timeout: 60_000, maxBuffer: 4 << 20 });
|
|
80
185
|
if (manifest.status !== 0) throw new Error(`origin/main public export manifest 읽기 실패: ${(manifest.stderr ?? '').trim()}`);
|
|
81
186
|
mkdirSync(join(snapshot, 'release'), { recursive: true });
|
|
82
187
|
writeFileSync(manifestPath, manifest.stdout ?? '');
|
|
@@ -94,7 +199,7 @@ export async function scanOriginMainExport(): Promise<{ mainSha: string; files:
|
|
|
94
199
|
}
|
|
95
200
|
}
|
|
96
201
|
const checkout = spawnSync('git', ['-C', snapshot, 'checkout-index', '--stdin', '-z', '-f'], {
|
|
97
|
-
input: `${[...needed].filter((file) => trackedSet.has(file)).join('\0')}\0`, encoding: 'utf8', maxBuffer: 16 << 20,
|
|
202
|
+
input: `${[...needed].filter((file) => trackedSet.has(file)).join('\0')}\0`, encoding: 'utf8', timeout: 60_000, maxBuffer: 16 << 20,
|
|
98
203
|
});
|
|
99
204
|
if (checkout.status !== 0) throw new Error(`origin/main export 입력 파일 준비 실패 rc=${checkout.status}: ${(checkout.stderr ?? '').trim().slice(0, 300)}`);
|
|
100
205
|
let output = '';
|
|
@@ -116,12 +221,121 @@ export async function scanOriginMainExport(): Promise<{ mainSha: string; files:
|
|
|
116
221
|
}
|
|
117
222
|
if (typeof report.files !== 'number' || typeof report.leaks !== 'number') throw new Error('public-export 검사 결과가 완전하지 않다');
|
|
118
223
|
if (code !== (report.leaks > 0 ? 1 : 0)) throw new Error(`public-export 종료 코드와 누수 결과 불일치: rc=${code} leaks=${report.leaks}`);
|
|
119
|
-
|
|
224
|
+
const codeLiterals = report.codeLiterals?.hits ?? 0;
|
|
225
|
+
return { mainSha, files: report.files, leaks: Math.max(0, report.leaks - codeLiterals), codeLiterals };
|
|
226
|
+
} finally {
|
|
227
|
+
rmSync(temp, { recursive: true, force: true });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Count added release-note lines from the last published tag to the exact pinned main commit. */
|
|
232
|
+
export async function scanReleaseNotesAddedLines(previousVersion: string | undefined, mainSha: string | undefined): Promise<number> {
|
|
233
|
+
if (!previousVersion || !STABLE.test(previousVersion)) throw new Error('직전 발행 판 버전이 없어 release/next.md 변경을 비교할 수 없다');
|
|
234
|
+
const repoRoot = runGit(process.cwd(), ['rev-parse', '--show-toplevel']);
|
|
235
|
+
const pinnedMainSha = mainSha && /^[0-9a-f]{40}$/u.test(mainSha)
|
|
236
|
+
? mainSha : runGit(repoRoot, ['rev-parse', '--verify', 'origin/main^{commit}']);
|
|
237
|
+
const previousTag = runGit(repoRoot, ['rev-parse', '--verify', `v${previousVersion}^{commit}`]);
|
|
238
|
+
const numstat = runGit(repoRoot, ['diff', '--numstat', `${previousTag}...${pinnedMainSha}`, '--', 'release/next.md']);
|
|
239
|
+
if (!numstat) return 0;
|
|
240
|
+
const added = Number(numstat.split('\t', 1)[0]);
|
|
241
|
+
if (!Number.isSafeInteger(added) || added < 0) throw new Error(`release/next.md diff 결과를 읽을 수 없다: ${numstat}`);
|
|
242
|
+
return added;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** Count files selected by the cutoff commit's public-export manifest that its own .gitignore would hide. */
|
|
246
|
+
export async function scanGitIgnoredExportAtCommit(commit: string): Promise<number | null> {
|
|
247
|
+
if (!/^[0-9a-f]{40}$/i.test(commit)) return null;
|
|
248
|
+
const repoRoot = runGit(process.cwd(), ['rev-parse', '--show-toplevel']);
|
|
249
|
+
if (!hasLocalCommit(repoRoot, commit)) return null;
|
|
250
|
+
const temp = mkdtempSync(join(tmpdir(), 'release-preflight-gitignored-'));
|
|
251
|
+
const snapshot = join(temp, 'checkout');
|
|
252
|
+
try {
|
|
253
|
+
runGit(temp, ['clone', '--shared', '--no-checkout', '--quiet', repoRoot, snapshot]);
|
|
254
|
+
runGit(snapshot, ['read-tree', commit]);
|
|
255
|
+
runGit(snapshot, ['checkout-index', '--all', '--force']);
|
|
256
|
+
const tracked = runGit(snapshot, ['ls-files', '-z']).split('\0').filter(Boolean);
|
|
257
|
+
const config = loadExportConfig(snapshot);
|
|
258
|
+
return gitIgnoredExportFiles(snapshot, selectExportFiles(tracked, config)).length;
|
|
120
259
|
} finally {
|
|
121
260
|
rmSync(temp, { recursive: true, force: true });
|
|
122
261
|
}
|
|
123
262
|
}
|
|
124
263
|
|
|
264
|
+
/** Build the PWA from an exact candidate commit in a disposable snapshot; never build the caller's dirty tree. */
|
|
265
|
+
export async function scanPwaBuildAtCommit(commit: string): Promise<{ ok: boolean; detail: string; commit: string }> {
|
|
266
|
+
if (!/^[0-9a-f]{40}$/i.test(commit)) return { ok: false, detail: `candidate commit is not pinned: ${commit || '(missing)'}`, commit };
|
|
267
|
+
const repoRoot = runGit(process.cwd(), ['rev-parse', '--show-toplevel']);
|
|
268
|
+
if (!hasLocalCommit(repoRoot, commit)) return { ok: false, detail: `candidate commit is unavailable locally: ${commit}`, commit };
|
|
269
|
+
const temp = mkdtempSync(join(tmpdir(), 'release-preflight-pwa-'));
|
|
270
|
+
const snapshot = join(temp, 'checkout');
|
|
271
|
+
const run = (cmd: string, args: string[], cwd: string, timeout = 60_000) => spawnSync(cmd, args, {
|
|
272
|
+
cwd, encoding: 'utf8', timeout, maxBuffer: SPAWN_SYNC_MAX_BUFFER,
|
|
273
|
+
});
|
|
274
|
+
const failureDetail = (result: ReturnType<typeof run>): string =>
|
|
275
|
+
(result.stderr || result.stdout || (result.error instanceof Error ? result.error.message : '') || `exit ${result.status}`).trim();
|
|
276
|
+
try {
|
|
277
|
+
const clone = run('git', ['clone', '--shared', '--no-checkout', '--quiet', repoRoot, snapshot], temp);
|
|
278
|
+
if (clone.status !== 0) return { ok: false, detail: `candidate checkout failed: ${failureDetail(clone)}`, commit };
|
|
279
|
+
const tree = run('git', ['read-tree', commit], snapshot);
|
|
280
|
+
if (tree.status !== 0) return { ok: false, detail: `candidate tree unavailable: ${failureDetail(tree)}`, commit };
|
|
281
|
+
const checkout = run('git', ['checkout-index', '--all', '--force'], snapshot);
|
|
282
|
+
if (checkout.status !== 0) return { ok: false, detail: `candidate files unavailable: ${failureDetail(checkout)}`, commit };
|
|
283
|
+
const rootInstall = run('bun', ['install', '--frozen-lockfile'], snapshot, 300_000);
|
|
284
|
+
if (rootInstall.status !== 0) return { ok: false, detail: `candidate dependency install failed: ${failureDetail(rootInstall).slice(-500)}`, commit };
|
|
285
|
+
const pwaInstall = run('bun', ['install', '--frozen-lockfile'], join(snapshot, 'apps/pwa'), 300_000);
|
|
286
|
+
if (pwaInstall.status !== 0) return { ok: false, detail: `PWA dependency install failed: ${failureDetail(pwaInstall).slice(-500)}`, commit };
|
|
287
|
+
const build = run('bun', ['bin/elanous.mjs', 'nexus', 'build'], snapshot, 15 * 60_000);
|
|
288
|
+
const detail = (build.status === 0 ? build.stdout : failureDetail(build)).trim().slice(-800);
|
|
289
|
+
// The snapshot was checked out at `commit`; echo that pin so injected evidence still has to match it.
|
|
290
|
+
return { ok: build.status === 0 && !build.error, detail: detail || `nexus build exit ${build.status}`, commit };
|
|
291
|
+
} catch (error) {
|
|
292
|
+
return { ok: false, detail: error instanceof Error ? error.message : String(error), commit };
|
|
293
|
+
} finally {
|
|
294
|
+
rmSync(temp, { recursive: true, force: true });
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** Read pool tags with a bounded SSH timeout; local pools use the image's immutable commit label. */
|
|
299
|
+
export function scanGateImageTag(commit: string, poolSpec: string): { checked: boolean; pools: string[]; detail?: string } {
|
|
300
|
+
if (!/^[0-9a-f]{40}$/i.test(commit)) return { checked: false, pools: [], detail: 'cutoff commit is not pinned' };
|
|
301
|
+
try {
|
|
302
|
+
const members = parsePodPool(poolSpec);
|
|
303
|
+
if (!members.length) return { checked: false, pools: [], detail: 'gate pool is empty' };
|
|
304
|
+
const pools: string[] = [];
|
|
305
|
+
const errors: string[] = [];
|
|
306
|
+
for (const member of members) {
|
|
307
|
+
if (!member.sshHost) {
|
|
308
|
+
const local = spawnSync('docker', ['image', 'inspect', 'elanous-harness:local', '--format', '{{index .Config.Labels "elanous.commit"}}'], { encoding: 'utf8', timeout: 10_000, maxBuffer: SPAWN_SYNC_MAX_BUFFER });
|
|
309
|
+
if (local.status === 0 && local.stdout.trim() === commit) pools.push(member.context);
|
|
310
|
+
else if (local.error || local.status === null) errors.push(`${member.context}: ${local.error?.message ?? 'docker inspection unavailable'}`);
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
const registry = member.registry ?? NODE_REGISTRY;
|
|
314
|
+
const portMatch = /:(\d+)$/.exec(registry);
|
|
315
|
+
if (!portMatch) {
|
|
316
|
+
errors.push(`${member.context}: registry port is missing (${registry})`);
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
// Remote registry tags use the 12-character prefix per pod-pool.ts; local elanous.commit labels are full SHAs.
|
|
320
|
+
const port = portMatch[1]!;
|
|
321
|
+
const image = 'elanous-harness';
|
|
322
|
+
const remote = spawnSync('ssh', ['-o', 'BatchMode=yes', '-o', 'ConnectTimeout=5', member.sshHost,
|
|
323
|
+
`curl -fsS --max-time 5 http://localhost:${port}/v2/${image}/tags/list 2>/dev/null`], { encoding: 'utf8', timeout: 12_000, maxBuffer: SPAWN_SYNC_MAX_BUFFER });
|
|
324
|
+
if (remote.status !== 0) {
|
|
325
|
+
errors.push(`${member.context}: registry query failed${remote.error ? ` (${remote.error.message})` : ` (rc=${remote.status})`}`);
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
try {
|
|
329
|
+
const tags = (JSON.parse(remote.stdout) as { tags?: string[] }).tags ?? [];
|
|
330
|
+
if (tags.includes(commit.slice(0, 12))) pools.push(member.context);
|
|
331
|
+
} catch { errors.push(`${member.context}: registry tag response was not valid JSON`); }
|
|
332
|
+
}
|
|
333
|
+
return { checked: errors.length === 0, pools, ...(errors.length ? { detail: errors.join('; ') } : {}) };
|
|
334
|
+
} catch (error) {
|
|
335
|
+
return { checked: false, pools: [], detail: error instanceof Error ? error.message : String(error) };
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
125
339
|
const POSTPUB_ID = /^POSTPUB-/i;
|
|
126
340
|
// Only a title that «starts» with the marker: a pre-publish cell often mentions «발행 뒤» in its body (a slot, a plan).
|
|
127
341
|
const POSTPUB_TITLE = /^[«"'(]?\s*(발행\s*(뒤|후)|post-?publish)/i;
|
|
@@ -149,6 +363,37 @@ export function cadencePublishAt(cutAt: string): string {
|
|
|
149
363
|
return new Date(cut).toISOString();
|
|
150
364
|
}
|
|
151
365
|
|
|
366
|
+
/** A read-only timeline; timestamps never imply that a human or automation completed a step. */
|
|
367
|
+
export function releaseCountdown(cutAt: string): ReleaseCountdownStep[] {
|
|
368
|
+
const cut = Date.parse(cutAt);
|
|
369
|
+
if (!Number.isFinite(cut)) throw new Error(`invalid release cut time: ${cutAt}`);
|
|
370
|
+
const phases = [
|
|
371
|
+
{ id: 'notes-and-readiness', offsetMinutes: -120, action: 'release/next.md 새 줄 ≥1 · 공개 export 누수 0 · 체크리스트 게이트 모의' },
|
|
372
|
+
{ id: 'pinned-main-preflight', offsetMinutes: -60, action: '컷 트리를 고정하고 origin/main export 및 release preflight 실행' },
|
|
373
|
+
{ id: 'gate-window', offsetMinutes: -30, action: 'gate 모드 · node-b 자리 확보 · 디스패처 정지 · podTarget 감축 확인' },
|
|
374
|
+
{ id: 'cut', offsetMinutes: 0, action: 'release 컷 실행' },
|
|
375
|
+
] as const;
|
|
376
|
+
return phases.map((phase) => ({
|
|
377
|
+
...phase,
|
|
378
|
+
at: new Date(cut + phase.offsetMinutes * 60_000).toISOString(),
|
|
379
|
+
observation: 'not-observed-by-countdown',
|
|
380
|
+
}));
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export function formatReleaseCountdown(cutAt: string, now = new Date(), readiness?: PreflightResult['countdownReadiness']): string[] {
|
|
384
|
+
return [
|
|
385
|
+
`발행 카운트다운 · 컷 ${formatKst(cutAt)} · 이 명령은 단계 완료를 관측하거나 실행하지 않는다`,
|
|
386
|
+
...releaseCountdown(cutAt).map((step) => {
|
|
387
|
+
const label = step.offsetMinutes === 0 ? 'T0' : `T${step.offsetMinutes}`;
|
|
388
|
+
const timing = now.getTime() < Date.parse(step.at) ? '예정' : '시각 도달 · 완료 미관측';
|
|
389
|
+
const t120 = readiness && step.id === 'notes-and-readiness'
|
|
390
|
+
? ` · read-only 판정 ${readiness.status} (notes +${readiness.notes.addedLines ?? '?'} · export 누수 ${readiness.export.leaks ?? '?'} · checklist ${readiness.checklist.status})`
|
|
391
|
+
: '';
|
|
392
|
+
return `· ${label} ${formatKst(step.at)} · ${timing} · ${step.action}${t120}`;
|
|
393
|
+
}),
|
|
394
|
+
];
|
|
395
|
+
}
|
|
396
|
+
|
|
152
397
|
/**
|
|
153
398
|
* A release's measured gate: the newest finished release-loop run of «that» version (`{ version }`) or, with
|
|
154
399
|
* `{ below }`, the most recently measured run of any version under it. Node timings are not stored in the run state, so the gate time is the start of the gate node's context file to
|
|
@@ -209,6 +454,9 @@ export async function releasePreflight(version: string, deps: PreflightDeps = {}
|
|
|
209
454
|
// Always the stored cut (before or after it): the preflight simulates «the cut», not the moment it is run.
|
|
210
455
|
const judgeAt = schedule ? new Date(Date.parse(schedule.cutAt)) : now;
|
|
211
456
|
const data = listChecklist(version);
|
|
457
|
+
const cutoffManifest = deps.countdown
|
|
458
|
+
? (deps.manifest ?? ((v: string) => readReleaseJson<CountdownManifest>(v, 'manifest.json')))(version)
|
|
459
|
+
: null;
|
|
212
460
|
const { autoMoved, ...gate } = cutChecklistGate(version, schedule?.landBy, judgeAt);
|
|
213
461
|
const p0 = data.items.filter((item) => item.status === 'yellow' && item.priority === 'P0').map((item) => item.id);
|
|
214
462
|
const byId = new Map(data.items.map((item) => [item.id, item]));
|
|
@@ -253,7 +501,8 @@ export async function releasePreflight(version: string, deps: PreflightDeps = {}
|
|
|
253
501
|
|
|
254
502
|
// 3a. Public export leakage is measured against one pinned origin/main commit, never this possibly dirty branch.
|
|
255
503
|
try {
|
|
256
|
-
|
|
504
|
+
const scanned = await (deps.exportCheck ?? scanOriginMainExport)();
|
|
505
|
+
exportCheck = scanned;
|
|
257
506
|
findings.push(exportCheck.leaks
|
|
258
507
|
? { level: 'block', check: 'export', message: `origin/main ${exportCheck.mainSha.slice(0, 12)} 공개 export 누수 ${exportCheck.leaks}건 · ${exportCheck.files} 파일 검사` }
|
|
259
508
|
: { level: 'ok', check: 'export', message: `origin/main ${exportCheck.mainSha.slice(0, 12)} 공개 export 누수 0건 · ${exportCheck.files} 파일 검사` });
|
|
@@ -264,15 +513,92 @@ export async function releasePreflight(version: string, deps: PreflightDeps = {}
|
|
|
264
513
|
|
|
265
514
|
// 3. The same input check `release run --dry-run` makes.
|
|
266
515
|
let runPrevious: string | undefined;
|
|
516
|
+
let gatePodPool: string | undefined;
|
|
267
517
|
try {
|
|
268
518
|
const result = await (deps.runInput ?? ((v: string) => runUnattendedRelease({ version: v, dryRun: true }, deps.releaseRunDeps ?? {})))(version);
|
|
269
519
|
const input = result.input as { previousVersion?: string; gatePodPool?: string };
|
|
270
520
|
runPrevious = input.previousVersion;
|
|
521
|
+
gatePodPool = input.gatePodPool;
|
|
271
522
|
findings.push({ level: 'ok', check: 'run-input', message: `release run --dry-run 입력 OK — 직전 판 ${input.previousVersion ?? '-'} · 게이트 풀 ${input.gatePodPool ?? '-'}` });
|
|
272
523
|
} catch (error) {
|
|
273
524
|
findings.push({ level: 'block', check: 'run-input', message: `release run --dry-run 입력 오류 — ${error instanceof Error ? error.message : String(error)}` });
|
|
274
525
|
}
|
|
275
526
|
|
|
527
|
+
let notesAddedLines: number | null = null;
|
|
528
|
+
let cutoffExportCheck: PreflightResult['cutoffExportCheck'] = null;
|
|
529
|
+
const manifestNotesEmpty = cutoffManifest?.escalate?.some((item) => item.kind === 'notes-empty') === true;
|
|
530
|
+
const fragments = cutoffManifest?.fragments;
|
|
531
|
+
const manifestNoteSummary = fragments
|
|
532
|
+
&& [fragments.byPr, fragments.byNextMd, fragments.unlinked, fragments.unknown].every(Number.isFinite)
|
|
533
|
+
? `PR ${fragments.byPr} · next.md ${fragments.byNextMd} · unlinked ${fragments.unlinked} · unknown ${fragments.unknown}`
|
|
534
|
+
: null;
|
|
535
|
+
if (deps.countdown) {
|
|
536
|
+
try {
|
|
537
|
+
notesAddedLines = await (deps.releaseNotesCheck ?? scanReleaseNotesAddedLines)(runPrevious, exportCheck?.mainSha);
|
|
538
|
+
findings.push(notesAddedLines > 0
|
|
539
|
+
? { level: 'ok', check: 'release-notes', message: `직전 발행 ${runPrevious ?? '-'} 이후 고정 main의 release/next.md 추가 ${notesAddedLines}줄` }
|
|
540
|
+
: { level: 'block', check: 'release-notes', message: `직전 발행 ${runPrevious ?? '-'} 이후 고정 main의 release/next.md 추가 줄이 없다` });
|
|
541
|
+
} catch (error) {
|
|
542
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
543
|
+
findings.push({ level: 'warn', check: 'release-notes', message: `release/next.md 변경을 측정하지 못했다 — ${message}` });
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const candidateCommit = cutoffManifest?.cutoff?.sha ?? '';
|
|
547
|
+
// cutoff.ts already classifies all public note sources (PR fragments and next.md lines) and emits
|
|
548
|
+
// notes-empty only when the manifest contains no user-visible feat/fix/security line.
|
|
549
|
+
const notesEmpty = manifestNotesEmpty;
|
|
550
|
+
const notesDetail = notesEmpty
|
|
551
|
+
? 'notes-empty — 컷 manifest가 공개용 feat/fix/security 노트가 없다고 판정했다'
|
|
552
|
+
: `컷 manifest 노트 분류: ${manifestNoteSummary ?? '확인 불가'}`;
|
|
553
|
+
const emptyManifestNotes = fragments !== undefined
|
|
554
|
+
&& fragments.byPr === 0 && fragments.byNextMd === 0 && fragments.unlinked === 0 && fragments.unknown === 0;
|
|
555
|
+
const cutoffNotesMissing = !candidateCommit || manifestNoteSummary === null || notesEmpty || emptyManifestNotes;
|
|
556
|
+
findings.push({
|
|
557
|
+
level: cutoffNotesMissing ? 'block' : 'ok', check: 'cutoff-notes',
|
|
558
|
+
message: notesDetail,
|
|
559
|
+
...(cutoffNotesMissing ? { suggestion: 'cutoff manifest를 만들고 고정 후보 트리의 release/next.md 에 사용자용 feat/fix 줄을 더한 뒤 다시 검사한다' } : {}),
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
let ignoredLeaks: number | null = null;
|
|
563
|
+
let ignoredLeaksError: string | undefined;
|
|
564
|
+
try { ignoredLeaks = await (deps.gitIgnoredExportCheck ?? scanGitIgnoredExportAtCommit)(candidateCommit); }
|
|
565
|
+
catch (error) {
|
|
566
|
+
ignoredLeaks = null;
|
|
567
|
+
ignoredLeaksError = error instanceof Error ? error.message : String(error);
|
|
568
|
+
}
|
|
569
|
+
cutoffExportCheck = { commit: candidateCommit, gitIgnoredLeaks: ignoredLeaks, ...(ignoredLeaksError ? { error: ignoredLeaksError } : {}) };
|
|
570
|
+
findings.push(ignoredLeaks === 0
|
|
571
|
+
? { level: 'ok', check: 'gitignored-export', message: `컷 후보 ${candidateCommit.slice(0, 12) || '(SHA 없음)'} public-export export-gitignored 0건` }
|
|
572
|
+
: { level: 'block', check: 'gitignored-export', message: ignoredLeaks === null || ignoredLeaks === undefined
|
|
573
|
+
? `컷 후보 ${candidateCommit.slice(0, 12) || '(SHA 없음)'} public-export export-gitignored 결과를 확인하지 못했다${ignoredLeaksError ? ` — ${ignoredLeaksError}` : ''}`
|
|
574
|
+
: `컷 후보 ${candidateCommit.slice(0, 12) || '(SHA 없음)'} public-export export-gitignored ${ignoredLeaks}건`,
|
|
575
|
+
suggestion: '공개 export 입력 중 .gitignore 에 걸리는 파일을 제거하거나 공개 정책을 고치고 검사를 다시 실행한다' });
|
|
576
|
+
|
|
577
|
+
let pwa: { ok: boolean; detail: string; commit?: string };
|
|
578
|
+
try {
|
|
579
|
+
pwa = await (deps.pwaBuildCheck ?? scanPwaBuildAtCommit)(candidateCommit);
|
|
580
|
+
const pwaOk = pwa.ok && pwa.commit === candidateCommit;
|
|
581
|
+
findings.push(pwaOk
|
|
582
|
+
? { level: 'ok', check: 'pwa-build', message: `PWA 빌드 통과 · ${candidateCommit.slice(0, 12)}` }
|
|
583
|
+
: { level: 'block', check: 'pwa-build', message: `PWA 빌드 실패 또는 SHA 불일치(${pwa.commit ?? '미기록'} ≠ ${candidateCommit}) · ${pwa.detail}`,
|
|
584
|
+
suggestion: `고정 후보 ${candidateCommit.slice(0, 12) || '(SHA 없음)'}에서 apps/pwa 빌드를 통과시킨다` });
|
|
585
|
+
} catch (error) {
|
|
586
|
+
pwa = { ok: false, detail: error instanceof Error ? error.message : String(error) };
|
|
587
|
+
findings.push({ level: 'block', check: 'pwa-build', message: `PWA 빌드 증거를 확인하지 못했다 · ${pwa.detail}`,
|
|
588
|
+
suggestion: `고정 후보 ${candidateCommit.slice(0, 12) || '(SHA 없음)'}에서 apps/pwa 빌드를 통과시킨다` });
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const imageCheck = gatePodPool
|
|
592
|
+
? (deps.gateImageTagCheck ?? scanGateImageTag)(candidateCommit, gatePodPool)
|
|
593
|
+
: { checked: false, pools: [], detail: 'release run 입력에서 gatePodPool을 얻지 못했다' };
|
|
594
|
+
findings.push(imageCheck.checked && imageCheck.pools.length > 0
|
|
595
|
+
? { level: 'ok', check: 'gate-image', message: `컷 후보 gate 이미지 태그 ${candidateCommit.slice(0, 12)} 확인 · 풀 ${imageCheck.pools.join(', ')}` }
|
|
596
|
+
: { level: 'block', check: 'gate-image', message: imageCheck.checked
|
|
597
|
+
? `컷 후보 gate 이미지 태그 ${candidateCommit.slice(0, 12) || '(SHA 없음)'}가 어느 풀에도 없다`
|
|
598
|
+
: `컷 후보 gate 이미지 태그를 확인하지 못했다 · ${imageCheck.detail ?? '원인 미상'}`,
|
|
599
|
+
suggestion: `컷 후보 ${candidateCommit.slice(0, 12) || '(SHA 없음)'}의 gate 이미지를 하나 이상의 풀에 빌드·반입한 뒤 preflight 를 다시 실행한다` });
|
|
600
|
+
}
|
|
601
|
+
|
|
276
602
|
// 4. Landing freeze (emergency switch since RELEASE-BRANCH): when on, `release run` refuses.
|
|
277
603
|
try {
|
|
278
604
|
const frozen = (deps.freeze ?? (() => readLandingFreeze(deps.releaseRunDeps?.freezeRoot)))();
|
|
@@ -320,10 +646,29 @@ export async function releasePreflight(version: string, deps: PreflightDeps = {}
|
|
|
320
646
|
}
|
|
321
647
|
}
|
|
322
648
|
|
|
649
|
+
const checklistHasResult = findings.some((f) => f.check === 'checklist' && f.level === 'ok');
|
|
650
|
+
const countdownReadiness: PreflightResult['countdownReadiness'] = !deps.countdown ? {
|
|
651
|
+
status: 'unmeasured', notes: { status: 'unmeasured', addedLines: null },
|
|
652
|
+
export: { status: 'unmeasured', leaks: null }, checklist: { status: 'unmeasured' },
|
|
653
|
+
} : (() => {
|
|
654
|
+
const notesFinding = findings.find((f) => f.check === 'release-notes');
|
|
655
|
+
const exportFinding = findings.find((f) => f.check === 'export');
|
|
656
|
+
const checklistBlocked = findings.some((f) => f.check === 'checklist' && f.level === 'block');
|
|
657
|
+
const notesStatus = notesFinding?.level === 'ok' ? 'green' : notesFinding?.level === 'block' ? 'red' : 'unmeasured';
|
|
658
|
+
const exportStatus = exportFinding?.level === 'ok' ? 'green' : exportFinding?.level === 'block' ? 'red' : 'unmeasured';
|
|
659
|
+
const checklistStatus = checklistBlocked ? 'red' : checklistHasResult ? 'green' : 'unmeasured';
|
|
660
|
+
const statuses = [notesStatus, exportStatus, checklistStatus];
|
|
661
|
+
return {
|
|
662
|
+
status: statuses.includes('red') ? 'red' : statuses.every((status) => status === 'green') ? 'green' : 'unmeasured',
|
|
663
|
+
notes: { status: notesStatus, addedLines: notesAddedLines },
|
|
664
|
+
export: { status: exportStatus, leaks: exportCheck?.leaks ?? null },
|
|
665
|
+
checklist: { status: checklistStatus },
|
|
666
|
+
};
|
|
667
|
+
})();
|
|
323
668
|
const blockers = findings.filter((f) => f.level === 'block').length;
|
|
324
669
|
const warnings = findings.filter((f) => f.level === 'warn').length;
|
|
325
670
|
debug.log('release.preflight', 'checked', { version, blockers, warnings });
|
|
326
|
-
return { version, next, verdict: blockers ? 'block' : warnings ? 'warn' : 'ok', blockers, warnings, findings, schedule: scheduleOut, exportCheck, previousVersion, gateMeasure, fallbackGateMeasure };
|
|
671
|
+
return { version, next, verdict: blockers ? 'block' : warnings ? 'warn' : 'ok', blockers, warnings, findings, schedule: scheduleOut, exportCheck, cutoffExportCheck, countdownReadiness, previousVersion, gateMeasure, fallbackGateMeasure };
|
|
327
672
|
}
|
|
328
673
|
|
|
329
674
|
const ICON: Record<PreflightLevel, string> = { ok: '✅', warn: '⚠️', block: '⛔' };
|
|
@@ -336,3 +681,192 @@ export function formatPreflight(result: PreflightResult): string[] {
|
|
|
336
681
|
: result.verdict === 'warn' ? `⚠️ 판정: 통과(경고 ${result.warnings})` : '✅ 판정: 통과';
|
|
337
682
|
return [`release preflight ${result.version} (다음 판 ${result.next})`, ...lines, verdict];
|
|
338
683
|
}
|
|
684
|
+
|
|
685
|
+
const COUNTDOWN_ORDER: CountdownStage[] = ['T-120', 'T-60', 'T-30', 'T0'];
|
|
686
|
+
const COUNTDOWN_OFFSET_MINUTES: Record<CountdownStage, number> = { 'T-120': 120, 'T-60': 60, 'T-30': 30, T0: 0 };
|
|
687
|
+
/** Which existing finding group each stage judges — the check vocabulary of `releasePreflight`, not a new rule set. */
|
|
688
|
+
const COUNTDOWN_CHECKS: Record<CountdownStage, PreflightCheck[]> = {
|
|
689
|
+
'T-120': ['checklist', 'postpub', 'notes', 'release-notes', 'export'],
|
|
690
|
+
'T-60': ['run-input', 'freeze', 'cut-tree', 'gate-slot', 'cutoff-notes', 'gitignored-export', 'pwa-build', 'gate-image'],
|
|
691
|
+
'T-30': ['schedule'],
|
|
692
|
+
T0: [],
|
|
693
|
+
};
|
|
694
|
+
const COUNTDOWN_LABELS: Record<CountdownStage, string> = {
|
|
695
|
+
'T-120': '노트(next.md 새 줄) · 공개 export 누수 · 체크리스트 게이트 모의',
|
|
696
|
+
'T-60': '컷 후보 P0 네 항목(노트 · gitignore export · PWA · gate 이미지) ⊕ node-b 게이트 자리',
|
|
697
|
+
'T-30': 'gate window 준비(컷→발행 창 ≥ 직전 실측 게이트)',
|
|
698
|
+
T0: '컷 트리 확인(실제 컷 실행은 별도)',
|
|
699
|
+
};
|
|
700
|
+
/** 담당 자리 — who is mailed when that stage goes red. */
|
|
701
|
+
const COUNTDOWN_OWNER: Record<CountdownStage, string> = { 'T-120': 'TC', 'T-60': 'OP', 'T-30': 'OP', T0: 'OP' };
|
|
702
|
+
/** 최후 수동 취소 구간(누리호 사상): 총책임자 8초 · LD 5초. */
|
|
703
|
+
const MANUAL_CANCEL_SECONDS = { totalResponsibleSeconds: 8, ldSeconds: 5 };
|
|
704
|
+
|
|
705
|
+
const worstLevel = (findings: PreflightFinding[]): PreflightLevel =>
|
|
706
|
+
findings.some((finding) => finding.level === 'block') ? 'block' : findings.some((finding) => finding.level === 'warn') ? 'warn' : 'ok';
|
|
707
|
+
|
|
708
|
+
function readReleaseJson<T>(version: string, file: string): T | null {
|
|
709
|
+
try { return JSON.parse(readFileSync(join(effectiveInstanceRoot(), 'release', version, file), 'utf8')) as T; } catch { return null; }
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/** T−120 — `next.md` got at least one new line for this release (0.2.39 found notes-empty only after the cut). */
|
|
713
|
+
function countdownNotes(manifest: CountdownManifest | null): PreflightFinding {
|
|
714
|
+
if (!manifest) return { level: 'warn', check: 'notes', message: 'next.md 새 줄 판정 없음 — 컷 manifest 없음(T−60 의 cutoff.ts 가 만든다)' };
|
|
715
|
+
const lines = (manifest.fragments?.byNextMd ?? 0) + (manifest.fragments?.unlinked ?? 0);
|
|
716
|
+
if ((manifest.escalate ?? []).some((item) => item.kind === 'notes-empty') || lines < 1) {
|
|
717
|
+
return {
|
|
718
|
+
level: 'block', check: 'notes', message: 'next.md 새 줄 0 — notes-empty(이 판 공개 노트가 비어 있다 · 컷 뒤에 드러나면 발행이 늦어진다)',
|
|
719
|
+
suggestion: 'release/next.md 에 이 판 feat/fix 줄을 더하고 cutoff.ts 로 manifest 를 다시 만든다',
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
return { level: 'ok', check: 'notes', message: `next.md 새 줄 ${lines}줄` };
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/** T−60 — the cut tree is pinned: the manifest names one cutoff sha (cutoff.ts output, its format unchanged). */
|
|
726
|
+
function countdownCutTree(manifest: CountdownManifest | null): PreflightFinding {
|
|
727
|
+
const sha = manifest?.cutoff?.sha;
|
|
728
|
+
return sha
|
|
729
|
+
? { level: 'ok', check: 'cut-tree', message: `컷 트리 고정 ${sha.slice(0, 12)}` }
|
|
730
|
+
: { level: 'warn', check: 'cut-tree', message: '컷 트리 고정 없음 — T−60 에 `bun scripts/release-loop/cutoff.ts --version <v>` 로 manifest 를 만든다' };
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/** T−60 — the «node-b» gate slot: dispatcher stopped and the pod target lowered so the gate can take a Pod. */
|
|
734
|
+
function countdownGateSlot(slot: CountdownGateSlot | null): PreflightFinding[] {
|
|
735
|
+
if (!slot) return [{ level: 'warn', check: 'gate-slot', message: 'node-b 게이트 자리 기록 없음 — release/<v>/gate-slot.json(디스패처 정지 · podTarget 감축)' }];
|
|
736
|
+
const findings: PreflightFinding[] = [];
|
|
737
|
+
if (slot.dispatcherPaused !== true) {
|
|
738
|
+
findings.push({
|
|
739
|
+
level: 'block', check: 'gate-slot', message: 'node-b 게이트 자리 — 디스패처가 멈추지 않았다(게이트가 Pod 를 잡지 못한다)',
|
|
740
|
+
suggestion: '디스패처를 정지하고 release/<v>/gate-slot.json 에 dispatcherPaused: true 를 남긴다',
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
if (slot.podTargetFrom !== undefined && slot.podTargetTo !== undefined && slot.podTargetTo >= slot.podTargetFrom) {
|
|
744
|
+
findings.push({
|
|
745
|
+
level: 'block', check: 'gate-slot', message: `node-b 게이트 자리 — podTarget 감축 없음(${slot.podTargetFrom}→${slot.podTargetTo})`,
|
|
746
|
+
suggestion: 'podTarget 을 게이트 몫만큼 줄이고 gate-slot.json 에 from/to 를 기록한다',
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
if (!findings.length) {
|
|
750
|
+
const target = slot.podTargetFrom !== undefined ? ` · podTarget ${slot.podTargetFrom}→${slot.podTargetTo ?? slot.podTargetFrom}` : '';
|
|
751
|
+
findings.push({ level: 'ok', check: 'gate-slot', message: `node-b 게이트 자리 확보 — 디스패처 정지${target}` });
|
|
752
|
+
}
|
|
753
|
+
return findings;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/** T0 — model readiness only: this command never executes the cut. */
|
|
757
|
+
function countdownCut(manifest: CountdownManifest | null): PreflightFinding {
|
|
758
|
+
const sha = manifest?.cutoff?.sha;
|
|
759
|
+
return sha
|
|
760
|
+
? { level: 'ok', check: 'cut-tree', message: `T0 컷 준비 — 고정 트리 ${sha.slice(0, 12)}(실제 컷 실행은 별도 명령)` }
|
|
761
|
+
: { level: 'block', check: 'cut-tree', message: 'T0 컷 불가 — 컷 트리 고정(manifest cutoff.sha)이 없다', suggestion: 'T−60 에 cutoff.ts 로 컷 트리를 고정한다' };
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function postSeatMail(mail: { from: string; to: string; subject: string; body: string }): void {
|
|
765
|
+
const result = spawnSync('eln', ['msg', 'post', '--from', mail.from, '--to', mail.to, '--kind', 'report', '--body', `${mail.subject}\n${mail.body}`], {
|
|
766
|
+
encoding: 'utf8', timeout: 60_000, maxBuffer: SPAWN_SYNC_MAX_BUFFER,
|
|
767
|
+
});
|
|
768
|
+
if (result.status !== 0 || result.error) process.stderr.write(`release countdown 좌석 우편 실패: ${(result.stderr || String(result.error ?? result.status)).trim()}\n`);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* `release preflight --countdown` — the launch sequence from T−120 to T0 over the same `releasePreflight` findings.
|
|
773
|
+
* A red stage stops every later stage, mails the owning seat and appends one «부적합 보고» per failure to the ledger.
|
|
774
|
+
* Read-only apart from that ledger append and the seat mail — the cut and the checklist are never changed.
|
|
775
|
+
*/
|
|
776
|
+
export async function runReleaseCountdown(version: string, deps: PreflightDeps = {}): Promise<CountdownResult> {
|
|
777
|
+
const preflight = await releasePreflight(version, { ...deps, countdown: true });
|
|
778
|
+
const now = deps.now ?? new Date();
|
|
779
|
+
const manifest = (deps.manifest ?? ((v: string) => readReleaseJson<CountdownManifest>(v, 'manifest.json')))(version);
|
|
780
|
+
const slot = (deps.gateSlot ?? ((v: string) => readReleaseJson<CountdownGateSlot>(v, 'gate-slot.json')))(version);
|
|
781
|
+
const cutAt = preflight.schedule?.cutAt ?? null;
|
|
782
|
+
|
|
783
|
+
const grouped = new Map<PreflightCheck, PreflightFinding[]>();
|
|
784
|
+
for (const finding of preflight.findings) grouped.set(finding.check, [...(grouped.get(finding.check) ?? []), finding]);
|
|
785
|
+
const own: Record<CountdownStage, PreflightFinding[]> = {
|
|
786
|
+
'T-120': [countdownNotes(manifest)],
|
|
787
|
+
'T-60': [countdownCutTree(manifest), ...countdownGateSlot(slot)],
|
|
788
|
+
'T-30': [],
|
|
789
|
+
T0: [countdownCut(manifest)],
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
const steps: CountdownStep[] = [];
|
|
793
|
+
let haltedAt: CountdownStage | null = null;
|
|
794
|
+
for (const stage of COUNTDOWN_ORDER) {
|
|
795
|
+
const offsetMinutes = COUNTDOWN_OFFSET_MINUTES[stage];
|
|
796
|
+
const at = cutAt ? new Date(Date.parse(cutAt) - offsetMinutes * 60_000).toISOString() : null;
|
|
797
|
+
if (haltedAt) {
|
|
798
|
+
steps.push({ stage, offsetMinutes, at, label: COUNTDOWN_LABELS[stage], level: 'ok', findings: [], skipped: true });
|
|
799
|
+
continue;
|
|
800
|
+
}
|
|
801
|
+
const findings = [...COUNTDOWN_CHECKS[stage].flatMap((check) => grouped.get(check) ?? []), ...own[stage]];
|
|
802
|
+
const level = worstLevel(findings);
|
|
803
|
+
steps.push({ stage, offsetMinutes, at, label: COUNTDOWN_LABELS[stage], level, findings, skipped: false });
|
|
804
|
+
if (level === 'block') haltedAt = stage;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
const nonconformance: NonconformanceEntry[] = steps.flatMap((step) => step.findings
|
|
808
|
+
.filter((finding) => finding.level === 'block')
|
|
809
|
+
.map((finding) => ({
|
|
810
|
+
version, stage: step.stage, cause: finding.message,
|
|
811
|
+
repair: finding.suggestion ?? `${step.stage} 담당 자리가 원인을 확인해 고친다`,
|
|
812
|
+
nextRelease: preflight.next, at: now.toISOString(),
|
|
813
|
+
})));
|
|
814
|
+
const nonconformanceLedger = deps.nonconformanceLog ?? join(effectiveInstanceRoot(), 'release', 'nonconformance.jsonl');
|
|
815
|
+
if (deps.allowSideEffects && nonconformance.length) {
|
|
816
|
+
mkdirSync(dirname(nonconformanceLedger), { recursive: true });
|
|
817
|
+
appendFileSync(nonconformanceLedger, `${nonconformance.map((entry) => JSON.stringify(entry)).join('\n')}\n`);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
const mail = haltedAt ? countdownMail(version, haltedAt, steps, nonconformance.length) : null;
|
|
821
|
+
if (deps.allowSideEffects && mail) (deps.postMail ?? postSeatMail)(mail);
|
|
822
|
+
|
|
823
|
+
const cancelSeconds = MANUAL_CANCEL_SECONDS.totalResponsibleSeconds + MANUAL_CANCEL_SECONDS.ldSeconds;
|
|
824
|
+
const manualCancel = cutAt ? {
|
|
825
|
+
windowStartAt: new Date(Date.parse(cutAt) - cancelSeconds * 1000).toISOString(),
|
|
826
|
+
windowEndAt: cutAt, ...MANUAL_CANCEL_SECONDS,
|
|
827
|
+
} : null;
|
|
828
|
+
const verdict = haltedAt ? 'block' : steps.some((step) => step.level === 'warn') ? 'warn' : 'ok';
|
|
829
|
+
debug.log('release.preflight', 'countdown', { version, haltedAt, verdict, nonconformance: nonconformance.length });
|
|
830
|
+
return {
|
|
831
|
+
version, next: preflight.next, cutAt, verdict, steps, haltedAt, mail, manualCancel, nonconformance, nonconformanceLedger,
|
|
832
|
+
sideEffectsEnabled: deps.allowSideEffects === true, preflight,
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function countdownMail(version: string, stage: CountdownStage, steps: CountdownStep[], failures: number): CountdownResult['mail'] {
|
|
837
|
+
const step = steps.find((entry) => entry.stage === stage)!;
|
|
838
|
+
const reasons = step.findings.filter((finding) => finding.level === 'block');
|
|
839
|
+
const repairs = reasons.filter((finding) => finding.suggestion).map((finding) => `· ${finding.message} → ${finding.suggestion}`);
|
|
840
|
+
return {
|
|
841
|
+
from: 'OP', to: COUNTDOWN_OWNER[stage], subject: `RELEASE-COUNTDOWN ${version} ${stage}`,
|
|
842
|
+
body: [
|
|
843
|
+
`RELEASE-COUNTDOWN ${version} ${stage} 빨강 — 발사 시퀀스를 자동 중지했다.`,
|
|
844
|
+
...reasons.map((finding) => `· ${finding.message}`),
|
|
845
|
+
...(repairs.length ? ['수리:', ...repairs] : []),
|
|
846
|
+
`«부적합 보고» ${failures}건을 원장에 넣었다(원인·수리·다음 판 ${nextPatch(version)} 반영).`,
|
|
847
|
+
].join('\n'),
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/** The one screen of the launch sequence: every stage green/red, the manual-cancel window and one «판정:» line. */
|
|
852
|
+
export function formatCountdown(result: CountdownResult): string[] {
|
|
853
|
+
const lines = [`release countdown ${result.version} (다음 판 ${result.next})${result.cutAt ? ` · 컷 ${formatKst(result.cutAt)}` : ''} · 검사 상태만 표시(단계 실행은 미관측)`];
|
|
854
|
+
for (const step of result.steps) {
|
|
855
|
+
const when = step.at ? ` · ${formatKst(step.at)}` : '';
|
|
856
|
+
if (step.skipped) { lines.push(`⏸ ${step.stage} — 미진행(${result.haltedAt} 빨강 — 자동 중지)${when}`); continue; }
|
|
857
|
+
lines.push(`${ICON[step.level]} ${step.stage} ${step.label}${when}`);
|
|
858
|
+
for (const finding of step.findings) {
|
|
859
|
+
lines.push(` ${ICON[finding.level]} ${finding.message}`);
|
|
860
|
+
if (finding.suggestion) lines.push(` → ${finding.suggestion}`);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
if (result.manualCancel) {
|
|
864
|
+
const seconds = Math.round((Date.parse(result.manualCancel.windowEndAt) - Date.parse(result.manualCancel.windowStartAt)) / 1000);
|
|
865
|
+
lines.push(`⏱ 최후 수동 취소 구간 T0−${seconds}초 ~ T0 (${formatKst(result.manualCancel.windowEndAt)}) — 총책임자 ${result.manualCancel.totalResponsibleSeconds}초 · LD ${result.manualCancel.ldSeconds}초`);
|
|
866
|
+
}
|
|
867
|
+
if (result.mail) lines.push(`${result.sideEffectsEnabled ? '📮 담당 자리 우편' : '📮 담당 자리 우편 계획(미발송 · 기본 dry-run)'} → ${result.mail.to} · ${result.mail.subject}`);
|
|
868
|
+
lines.push(result.verdict === 'block'
|
|
869
|
+
? `⛔ 판정: 평가 중지 — ${result.haltedAt} 빨강 · 부적합 보고 ${result.nonconformance.length}건 ${result.sideEffectsEnabled ? `원장 기록(${result.nonconformanceLedger})` : `원장 기록 계획(미기록 · 기본 dry-run: ${result.nonconformanceLedger})`}`
|
|
870
|
+
: result.verdict === 'warn' ? '⚠️ 판정: 통과(경고) — 경고 단계는 기록만 한다' : '✅ 판정: 통과');
|
|
871
|
+
return lines;
|
|
872
|
+
}
|