elanous 0.2.22 → 0.2.23
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/NOTICE +7 -0
- package/README.md +32 -22
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/{119fc678.6f591cdb84ca5791.js → 0673cc54.8e2ef1c23e49e190.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{a9f2b54d-40c16bd0efdb04b8.js → 0a09e062-34f747a17d8d8c2e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{f8626309.a4e5d362b1d55e59.js → 0e3acd34.d42b2ef87fdf4234.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1168.a0fb3d9db76c00bd.js +77 -0
- package/apps/pwa/out/_next/static/chunks/{7175-350f4ce2873bc038.js → 1325-dc3f3c21eb181cb4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1357.a843c41d2cd7ec31.js +34 -0
- package/apps/pwa/out/_next/static/chunks/1430.53f49882b17607c2.js +24 -0
- package/apps/pwa/out/_next/static/chunks/1459-be6509b3419d8a9f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6767.8e6c5f6e744fcd9f.js → 1505.5826671109111e75.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/153.0a4e3cd516930491.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1572.7bc291352e241a45.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1593-d07ad068c120dfb0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1608.b7c900baa56cab32.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1615-4d8156febaac4b35.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1646.e03f0fbc26bc72f6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7327-2d69fb321ba73389.js → 1669-da843ed73aa301f5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/176.13ba0659e3e96fd4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1990.9c60f495ef33c070.js +36 -0
- package/apps/pwa/out/_next/static/chunks/2042e469.f86158dd5c1e51a1.js +122 -0
- package/apps/pwa/out/_next/static/chunks/2528.0af5c0e1fad88df6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2543-28ec6aeb80a5e1e1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2561.43c99d6ee914ee6a.js +62 -0
- package/apps/pwa/out/_next/static/chunks/2638.b39ab2f674caa1a5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/272-b23d2e92161d11fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7899.597af4a9996c907f.js → 2721.bc9a8f36bd85c07a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2778.67e0c1d484189ccf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2841.10ff309f6a3168af.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2886-9639f97d523d752a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2978.57fb87ce21cc5a85.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3664.15f968c39d15bd28.js +149 -0
- package/apps/pwa/out/_next/static/chunks/3786-935eb3fcde90e496.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3813.df4a01ae89c6f63f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3961-fbef064c213b2b18.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3992.f79c70480e8c4077.js +309 -0
- package/apps/pwa/out/_next/static/chunks/40-413e8641de9ed1e5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4054-521f11a503daed64.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4315.e7356f34c7d8ec79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4489-bf629de6f04f7681.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4540.557733cc88c6a82b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/462.0275b4338c5b6337.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4658-7ed9266098a787e4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4945-0f4046cfc3228ea3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{d70879db.c66c56a95a91efc6.js → 4c5e95ce.83e9b16add6fcd8c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5105-18b4165c5d3a86b0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5205-6992210fe31f45f4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5287-72898b126d722457.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6158.566c890adc06a459.js → 5313.1697309c532b9856.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/{79b78388.663188133449d707.js → 53746282.1be36038d1abd949.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5474.991b90688c3cb321.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5544-871146192d095594.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5578.ac963634373b28c6.js +286 -0
- package/apps/pwa/out/_next/static/chunks/5935.d5e8ca3e096772e8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5967-c0a80f8ffe5d4050.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1268.0840ff91e083fcbd.js → 5974.360f8441eb0ac1ec.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6006.84e8b418a7a0db97.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6013-1267ba7454d6764b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6038-1e0f7ee1086da4cf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/617.709dd0019fe99796.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6261.fb9bf94947094636.js +74 -0
- package/apps/pwa/out/_next/static/chunks/6411.1966866fae31ffa9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6481-b829d99aa8d158f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6541.2b7daa043100d0fa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6571-926ddf024a6ad5df.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6597.2d7a6d77ab485b27.js +64 -0
- package/apps/pwa/out/_next/static/chunks/67.5dd2dc543c2fa3de.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9832.2ab2e818013574c7.js → 6702.ebbd165d5e4a4a14.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6777-312496b0d774719b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/681.0116f7036cfb4e8d.js +32 -0
- package/apps/pwa/out/_next/static/chunks/6871.30b2bf23b3126e36.js +24 -0
- package/apps/pwa/out/_next/static/chunks/6882.737d366d54e29d83.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7035.54c2c535164b1c9b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7086-8bfa7e8f796a03d9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7126.5e5599f251726b67.js +93 -0
- package/apps/pwa/out/_next/static/chunks/7203-47ff0740a293e284.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7268.017f49dafb5f0ff3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7355.5d65a87e9567ae08.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7392.4018c2a62177f296.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6110.1297d4093fdfbb0d.js → 740.90b31555290236a9.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/7471.3a3d1be50559cfb8.js +148 -0
- package/apps/pwa/out/_next/static/chunks/7500.73489a345cef2201.js +226 -0
- package/apps/pwa/out/_next/static/chunks/{9070.067c106528ec1ae6.js → 7518.49161e8f5814abdb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7565.c2f72a15c1ed0396.js +27 -0
- package/apps/pwa/out/_next/static/chunks/7651.6d1c984d3d8587a4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7713.7ab863ed59b2d661.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7793.d7cd2026767ddd54.js +115 -0
- package/apps/pwa/out/_next/static/chunks/7837.c7c267e5dceadb11.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7891.09266382daf70d1c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7905.b230b9d06cb4ad5d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7990-cbf6a01bcbdcb7e6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8119-819c549e0c197786.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8186-74ea19a3700d04a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8194.484f51e273d9abd2.js → 8264.00e434fbd02e9311.js} +16 -16
- package/apps/pwa/out/_next/static/chunks/8267-e7b3262b735ae106.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8386.86518e55b75f7cf5.js +8 -0
- package/apps/pwa/out/_next/static/chunks/{088d8e8a.8069543d082bec12.js → 83cfc5c8.349535543c1548b6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8437.ba6eeeb29590042f.js +23 -0
- package/apps/pwa/out/_next/static/chunks/{1948.5f35b39eef222a94.js → 8438.ee7f0df3b3f6fcb4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8469-1500be57e582296f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8483-7164cecdaf178ca6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{4588.ad3364cb7fb97ada.js → 8532.a86e99acf9b761cb.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/8569.b853a8daa8f31d12.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8722-4c940f02c496ad36.js +1 -0
- package/apps/pwa/out/_next/static/chunks/887-41d0dcf5af48c628.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8925-2abfb48af477fb7f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{88729267.9c6c000157c83f2f.js → 89849432.9eaa2f4c9c010965.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/9039.da86361496797522.js +90 -0
- package/apps/pwa/out/_next/static/chunks/905.d5745a25884754d4.js +94 -0
- package/apps/pwa/out/_next/static/chunks/9089-d909f3d4478b0628.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9096.dc5c7a267f83790c.js +41 -0
- package/apps/pwa/out/_next/static/chunks/93.a567257b6bdb7fe4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9320.419acb9665b46597.js +287 -0
- package/apps/pwa/out/_next/static/chunks/9356.b8ceda6634612ff8.js +79 -0
- package/apps/pwa/out/_next/static/chunks/94.e9e1043b35cd3eed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9400-7498ea90b2e6ee8f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3522-1de1319d028ac89c.js → 9502-32c5dc22e8f120f5.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/9930-58644af9c813d6e1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9932.367cf4725fedd6e2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9935.1628b904cdc8ce0e.js +104 -0
- package/apps/pwa/out/_next/static/chunks/{7630.2b9a68659053ff4e.js → 9976.8f28864317639fe6.js} +16 -16
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-a2aa8709d193ee0d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-d5341ac96203a983.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-0160ab83c8685a12.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-55c631c032dcc47e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-ad3bd8084048bee4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-e6784759a2531031.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-5c5d0e370f495ef6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-c325e21543cd48a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/{page-80ed511e02c81412.js → page-6168a7c43e6ef8f5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-31417d3604154a6d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-e9606dc0bdec9305.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-a648bc464c9dc754.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-25bb425afdb27541.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-32d72fdef1afe11a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-d27b794c8ba76bbb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-a2aa8709d193ee0d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-2bd9ce9c020d02d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-a34096245e63b9f2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-22170f4f00a15496.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-4cc66d22ca4ec502.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-83b14c729fcea837.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-6e2781c523dad761.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-26dd415c8f13f341.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-ccdb83ed40c8d724.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-7ad3de8fa06fdea9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-015c02c4fec4023e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-6b0ae5b5c42f6d2f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-786111f30b6f0969.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-48fb31810693c357.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-4e17cce5e2ed0fee.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-cb154acadb025414.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-16258e956ea93fc2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-c2dd2b19c93f90ab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-85f35dd1dd96ae87.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-d3c007c8a0ddd7ee.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-7837ce2aacb97b04.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-7350ee93f4fe1033.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-00723458575f2b19.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-a2aa8709d193ee0d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-745d9b6c015076f8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-736d33c7414a8a1a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-119be9a0444d2482.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-fa806c018833e332.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-c782041b5f5199ba.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-d431fbdea6ad93dd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-0c056abff82b9891.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-242d668e4fbf72c4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-847c3de6ca8a4416.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-81dd0d8ae923ed72.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-74e69ba2c8e970d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-70038f41ae7cf931.js +1 -0
- package/apps/pwa/out/_next/static/chunks/b4e12462-5fab4a813a63dadd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/cd39fa05.fac4545f42a3ef70.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{f6b7c9ba.64a895b8b2fedbdc.js → ce383d63.d2ede338788ed18e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{framework-5c169757551134ed.js → framework-b2763b10dc6b3836.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/main-app-27893e93b0ec193d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-f280047194e1daf8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-e90eff2d0b70564c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-70f2d654b3435d34.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-4794f053bd5e4230.js +1 -0
- package/apps/pwa/out/_next/static/css/5619d59e87eddf66.css +1 -0
- package/apps/pwa/out/_next/static/css/d0a48dc099ed86cf.css +1 -0
- package/apps/pwa/out/_next/static/{aBi6EyMtLsrl7J8eH4kF7 → rfkMm4movqkqMebBrCXiR}/_buildManifest.js +1 -1
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +23 -23
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +23 -23
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +23 -23
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +22 -22
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +22 -22
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +23 -23
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +22 -22
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +23 -23
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +23 -23
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +22 -22
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +23 -23
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +24 -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 +24 -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/models/anthropic/claude-haiku-5-5.yaml +32 -0
- package/catalog/models/anthropic/claude-opus-5-5.yaml +5 -0
- package/catalog/models/anthropic/claude-sonnet-5-5.yaml +31 -0
- package/catalog/models/anthropic/claude-sonnet-5.yaml +8 -2
- package/catalog/models/openai/gpt-6-astra.yaml +6 -0
- package/catalog/models/openai/gpt-6-luna.yaml +6 -0
- package/catalog/models/openai/gpt-6-sol.yaml +6 -0
- package/graphs/log-horizon/log-horizon-watch.yaml +25 -0
- package/graphs/log-horizon/recipes.yaml +3 -0
- package/graphs/release/recipes.yaml +1 -1
- package/graphs/release/release-loop.yaml +6 -2
- package/graphs/release-rehearsal/recipes.yaml +12 -0
- package/graphs/release-rehearsal/release-rehearsal.yaml +17 -0
- package/package.json +4 -1
- package/scripts/bench-report.ts +82 -3
- package/scripts/ci-typecheck-changed.ts +10 -1
- package/scripts/hq/hq-fence-wrapper.sh +148 -0
- package/scripts/lib/typecheck-ratchet.ts +51 -0
- package/scripts/openai-relay-server.ts +5 -1
- package/scripts/release-loop/docs-node.ts +258 -43
- package/scripts/release-loop/gate-method.ts +59 -0
- package/scripts/release-loop/gate-node.ts +73 -7
- package/scripts/release-loop/gate-wall.ts +208 -0
- package/scripts/release-loop/known-issues-node.ts +53 -8
- package/scripts/release-loop/publish-at.ts +14 -0
- package/scripts/release-loop/publish-node.ts +17 -2
- package/scripts/release-loop/resume-release.ts +34 -4
- package/scripts/release-loop/resume.ts +53 -0
- package/scripts/release-loop/shard-plan.ts +3 -11
- package/scripts/release-loop/tui-node-checks.ts +22 -0
- package/scripts/release-loop/unattended-release.ts +8 -1
- package/scripts/rhythm/daily.ts +51 -2
- package/scripts/rhythm/weekly.ts +3 -2
- package/scripts/tsc-main-safety-net.ts +189 -0
- package/scripts/usage-rollup.ts +8 -4
- package/src/acp/codex-plugins.ts +48 -6
- package/src/agent/monad-agent-turn.ts +67 -5
- package/src/agent/run-context.ts +10 -0
- package/src/agent/tool-profile.ts +28 -2
- package/src/agent-mission/acp-judge.ts +8 -2
- package/src/agent-mission/capabilities-cli.ts +16 -1
- package/src/agent-mission/driver.ts +1 -1
- package/src/agent-mission/pr-review-watch.ts +6 -2
- package/src/agent-mission/review-loop.ts +33 -5
- package/src/agent-substrate/acp-reviewer.ts +17 -8
- package/src/agent-substrate/pr-reviewer.ts +164 -28
- package/src/agent-substrate/review-intent.ts +6 -4
- package/src/agent-substrate/self-review-cli.ts +111 -16
- package/src/agent-substrate/shard-boundary-intent.ts +92 -26
- package/src/ask-user-question/pending-questions.ts +8 -2
- package/src/autopilot/build/llm-conflict-merge.ts +216 -12
- package/src/autopilot/codebase-grounding.ts +44 -0
- package/src/autopilot/llm-control-brain.ts +13 -1
- package/src/autopilot/mission-codebase-gate.ts +8 -77
- package/src/autopilot/pr-manager.ts +12 -3
- package/src/boot/acp-server.ts +4 -7
- package/src/boot/acp-socket-path.ts +7 -0
- package/src/briefing/brief-items.ts +21 -13
- package/src/chat/index.ts +12 -3
- package/src/cli/agent-cli.ts +20 -0
- package/src/cli/brief-cli.ts +27 -21
- package/src/cli/cli-user-error.ts +9 -2
- package/src/cli/log-event-names.ts +3 -0
- package/src/cli/logs-cli.ts +1 -0
- package/src/cli/mcp-login.ts +111 -37
- package/src/cli/plugin-cli.ts +21 -1
- package/src/cli/pod-cli.ts +26 -0
- package/src/cli/pr-cli.ts +30 -2
- package/src/cli/pr-land-overlap-evidence.ts +131 -0
- package/src/cli/release-cli.ts +53 -11
- package/src/cli/self-update.ts +173 -10
- package/src/cli/skills-cli.ts +12 -3
- package/src/cli/tasks-cli.ts +133 -27
- package/src/context-bus/context-first.ts +51 -2
- package/src/context-bus/context-now-surfaces.ts +13 -2
- package/src/context-bus/long-term-memory.ts +3 -1
- package/src/context-bus/nightly-condense.ts +3 -2
- package/src/core-turn/run-goal-loop.ts +86 -2
- package/src/dashboard/chat-fold.ts +20 -0
- package/src/dashboard/first-screen-band.ts +151 -1
- package/src/dashboard/index.ts +27 -5
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +5 -3
- package/src/dashboard/stale-install-check.ts +13 -2
- package/src/dashboard/turn-stream-formatter.ts +25 -0
- package/src/debug/log-diet.ts +159 -0
- package/src/debug/log.ts +52 -17
- package/src/display/coordinator.ts +25 -2
- package/src/domains/coo-admin-tool.ts +27 -0
- package/src/domains/cron-match.ts +1 -0
- package/src/domains/outbound-alert.ts +17 -6
- package/src/domains/release-tool.ts +119 -10
- package/src/domains/schedule-registry.ts +10 -7
- package/src/domains/schedule-runner.ts +1 -0
- package/src/elanous-config-dir.ts +2 -2
- package/src/elanous-daemon.ts +3 -3
- package/src/execution-loop/launch-gate.ts +1 -1
- package/src/expression/i18n/messages.en.ts +5 -0
- package/src/expression/i18n/messages.ja.ts +5 -0
- package/src/expression/i18n/messages.ko.ts +5 -0
- package/src/expression/i18n/messages.zh.ts +5 -0
- package/src/expression/i18n/types.ts +8 -0
- package/src/github/pr-labels.ts +1 -0
- package/src/graph-runner/runner.ts +54 -5
- package/src/harness/dispatch-backpressure.ts +259 -0
- package/src/harness/harness-cli-command.ts +107 -28
- package/src/harness/harness-seams.ts +2 -20
- package/src/harness/harness-substrate-default.ts +72 -1
- package/src/harness/nested-elanous-policy.ts +4 -2
- package/src/hq/fence-wrapper.ts +7 -0
- package/src/hq/hq.ts +33 -2
- package/src/hq/lease.ts +13 -0
- package/src/hq/seats.ts +114 -18
- package/src/index.ts +163 -64
- package/src/intake-plane/collect-github.ts +19 -1
- package/src/intake-plane/digest.ts +45 -20
- package/src/intake-plane/lens.ts +36 -8
- package/src/intelligence-map/model-alias.ts +6 -0
- package/src/intelligence-map/model-catalog.ts +41 -0
- package/src/knowledge/kgs/pack.ts +5 -2
- package/src/knowledge/kgs/sqlite-store.ts +15 -0
- package/src/knowledge/tools/knowledge-query.ts +20 -2
- package/src/knowledge/write.ts +234 -2
- package/src/llm/bedrock-provider.ts +175 -0
- package/src/llm/bedrock.ts +345 -0
- package/src/llm/provider-credentials.ts +3 -1
- package/src/llm.ts +306 -35
- package/src/loops/budget.ts +150 -0
- package/src/loops/orchestrator/seat-share.ts +6 -49
- package/src/loops/orchestrator/traffic.ts +5 -81
- package/src/market/signed-index.ts +42 -0
- package/src/mcp/mcp-oauth.ts +136 -17
- package/src/model-tier/llm-tier-map.ts +48 -4
- package/src/models/prompts.ts +22 -1
- package/src/mss/logging/horizon-watch.ts +168 -0
- package/src/mss/logging/log-store.ts +204 -15
- package/src/native-tool-catalog.ts +1 -1
- package/src/nexus/api/consult-requests.ts +7 -10
- package/src/nexus/api/discord-trigger-bot.ts +6 -3
- package/src/nexus/api/error-reports.ts +30 -8
- package/src/nexus/api/http-server.ts +8 -0
- package/src/nexus/api/loop-resources.ts +42 -0
- package/src/nexus/api/outbound-report.ts +8 -3
- package/src/nexus/api/telegram-trigger-bot.ts +6 -3
- package/src/nexus/index.ts +10 -0
- package/src/nexus/outbound/format.ts +2 -0
- package/src/nexus/outbound/router.ts +198 -13
- package/src/oauth/codex-account-rotation.ts +16 -18
- package/src/oauth/codex-account-store.ts +134 -5
- package/src/oauth/fallback-chain.ts +28 -6
- package/src/onboarding.ts +2 -1
- package/src/plugins/adapters/claude-package.ts +4 -12
- package/src/plugins/core/capability-policy.ts +141 -0
- package/src/plugins/core/host.ts +21 -8
- package/src/plugins/core/manifest.ts +19 -2
- package/src/plugins/core/trust-store.ts +15 -0
- package/src/plugins/core/types.ts +12 -0
- package/src/plugins/install/market-fetch.ts +68 -16
- package/src/plugins/install/plugin-install.ts +187 -16
- package/src/plugins/maker/plugin-maker.ts +25 -20
- package/src/plugins/maker/wizard-generate.ts +16 -2
- package/src/policy/llm-policy.ts +14 -1
- package/src/process/pid-liveness.ts +1 -1
- package/src/prompt-cache/openai.ts +10 -6
- package/src/prompt-cache/openrouter.ts +94 -0
- package/src/prompt-enhance/enhance.ts +46 -5
- package/src/prompt-library/family-prompt-coverage.ts +1 -0
- package/src/prompt-library/universal-preamble.ts +2 -2
- package/src/registry/discovery/openrouter-live-resolve.ts +122 -0
- package/src/registry/loader.ts +6 -1
- package/src/release-loop/checklist.ts +24 -9
- package/src/release-loop/feature-store.ts +3 -0
- package/src/release-loop/gate-shards.ts +48 -4
- package/src/release-loop/landed-to-green.ts +81 -0
- package/src/release-loop/light-rc.ts +11 -1
- package/src/release-loop/merged-pr-checklist.ts +10 -0
- package/src/release-loop/placement.ts +91 -22
- package/src/release-loop/rehearsal.ts +317 -0
- package/src/release-loop/release-schedule.ts +13 -1
- package/src/release-loop/rubric-types.ts +1 -0
- package/src/release-loop/rubric.ts +2 -2
- package/src/retro/daily.ts +23 -9
- package/src/seat-loop/seat-loop.ts +34 -1
- package/src/seat-loop/traffic-signal.ts +44 -0
- package/src/self-dev/ask-launch-flow.ts +2 -2
- package/src/self-dev/dev-cli.ts +67 -3
- package/src/self-dev/dev-pipeline.ts +4 -6
- package/src/self-dev/draft-residue.ts +463 -0
- package/src/self-dev/draft-sweep.ts +151 -10
- package/src/self-dev/draft-triage-rules.ts +27 -1
- package/src/self-dev/launch-preflight-text.ts +549 -0
- package/src/self-dev/launch-preflight.ts +10 -545
- package/src/self-dev/orchestrate-cli.ts +57 -2
- package/src/self-dev/orchestrate-summary-line.ts +13 -0
- package/src/self-dev/orchestrate.ts +8 -135
- package/src/self-dev/review-provider-fallback.ts +115 -19
- package/src/self-dev/run-store.ts +1 -3
- package/src/self-dev/run-supervisor.ts +3 -22
- package/src/self-dev/run-types.ts +178 -0
- package/src/self-dev/shard-aggregate.ts +61 -13
- package/src/self-dev/shard-plan.ts +5 -1
- package/src/self-dev/shard-run.ts +23 -5
- package/src/self-dev/supervisor-verdict-edges.ts +2 -4
- package/src/self-implement/blocked-draft-policy.ts +3 -19
- package/src/self-implement/draft-on-stop-mode.ts +18 -0
- package/src/self-implement/follow-up-goals.ts +114 -38
- package/src/self-implement/gate-baseline-reap.ts +157 -0
- package/src/self-implement/gate-baseline.ts +78 -16
- package/src/self-implement/gate-cli.ts +12 -2
- package/src/self-implement/gate-remote.ts +17 -6
- package/src/self-implement/goal-author.ts +24 -4
- package/src/self-implement/goal-digest.ts +19 -0
- package/src/self-implement/graph-overlay-condition.ts +4 -0
- package/src/self-implement/headless-elanous-driver.ts +11 -9
- package/src/self-implement/host-regate.ts +120 -29
- package/src/self-implement/l8-merge-queue.ts +67 -5
- package/src/self-implement/merge-train.ts +131 -0
- package/src/self-implement/off-diff-evidence.ts +9 -0
- package/src/self-implement/orchestrator.ts +133 -16
- package/src/self-implement/review-finding-recurrence.ts +28 -0
- package/src/self-implement/rework-policy.ts +2 -1
- package/src/self-implement/round-summary-card.ts +63 -0
- package/src/self-implement/run-ledger.ts +4 -3
- package/src/self-implement/run-step-projection.ts +147 -0
- package/src/self-implement/run-stop.ts +1 -1
- package/src/self-implement/seams.ts +36 -3
- package/src/self-implement/self-implement-cli.ts +3 -1
- package/src/self-implement/train-gate-remote.ts +158 -0
- package/src/session-runtime/index.ts +10 -1
- package/src/session-runtime/retry-api.ts +11 -1
- package/src/skills/gift-install.ts +119 -0
- package/src/skills/runner.ts +59 -2
- package/src/skills/skill-feedback.ts +31 -1
- package/src/skills/skill-tier.ts +1 -0
- package/src/smart-wizard/capability-index.ts +231 -0
- package/src/smart-wizard/rung.ts +74 -0
- package/src/steward/launch.ts +20 -9
- package/src/steward/triage.ts +4 -2
- package/src/storage/file-lock.ts +8 -1
- package/src/task-agent/actions.ts +4 -0
- package/src/task-agent/card-evidence.ts +59 -13
- package/src/task-agent/judge.ts +4 -3
- package/src/task-agent/land-worktree.ts +201 -0
- package/src/task-agent/live-move-history.ts +133 -0
- package/src/task-agent/live-moves.ts +106 -19
- package/src/task-agent/parent-host.ts +117 -8
- package/src/task-agent/rejudge-on-head.ts +297 -0
- package/src/task-agent/review-result-capture.ts +218 -0
- package/src/task-agent/shadow.ts +103 -7
- package/src/task-agent/task-hand.ts +136 -11
- package/src/task-orchestrator/surfaces/pod-child-providers.ts +29 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +6 -3
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +16 -2
- package/src/task-orchestrator/surfaces/pod-image-source.ts +212 -0
- package/src/task-orchestrator/surfaces/pod-lease.ts +93 -12
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +25 -2
- package/src/task-orchestrator/surfaces/pod-pool.ts +66 -14
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +259 -36
- package/src/task-orchestrator/surfaces/self-implement.ts +6 -2
- package/src/telegram-service.ts +5 -1
- package/src/telegram.ts +17 -4
- package/src/time/format.ts +13 -4
- package/src/tool-runtime/goal-author-runtime.ts +1 -0
- package/src/tool-runtime/self-cognition-catalog.ts +23 -0
- package/src/tool-runtime/self-cognition-runtimes.ts +2 -24
- package/src/user-config.ts +132 -24
- package/src/ux-sim/slash-menu-frames.ts +168 -0
- package/src/version/packed-revision.json +1 -1
- package/src/web-terminal/preview-tap-registry.ts +5 -2
- package/src/workflow-runtime/triggers/scheduler.ts +1 -0
- package/apps/pwa/out/_next/static/chunks/1109.78a7a3288c0259d9.js +0 -148
- package/apps/pwa/out/_next/static/chunks/115.1c6690b8604f81c4.js +0 -32
- package/apps/pwa/out/_next/static/chunks/1262.76a3f08e67c5f816.js +0 -309
- package/apps/pwa/out/_next/static/chunks/1307-08b716476f1d50af.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1358d292.5c87f12cad6eaa91.js +0 -122
- package/apps/pwa/out/_next/static/chunks/1438.27a1a7b8f2308004.js +0 -149
- package/apps/pwa/out/_next/static/chunks/1442.02d85cd88f4c1eac.js +0 -79
- package/apps/pwa/out/_next/static/chunks/1501-1aad662d82ba43f7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1584.9e2dd53da6e6d4b1.js +0 -24
- package/apps/pwa/out/_next/static/chunks/1586.b989b0614afb77e6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1609.2ad355062870151f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1612-f6df0fc0eed7157b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1923.37f1d5fe7ebe3860.js +0 -27
- package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1993.3fd074a2a246234e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/211-9c9090c59e2c5907.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2140.d2daf9ee96e295d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2212.0359851457473efc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/224.583e562365a52f74.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2328.0f37465646bafa7d.js +0 -36
- package/apps/pwa/out/_next/static/chunks/2372.bf71372184991937.js +0 -286
- package/apps/pwa/out/_next/static/chunks/2489-d238d3e2eb5b2bf5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2537-6c289376caa88ca1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2549-a8515c86f9e8cb96.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2655.72cc6a97276c5acc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2743-ef6e8c3ced74cce5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2840-5458ad50c49cac6a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2856.f75d5baef7f49b74.js +0 -1
- package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3344.0de4b0ec5a454b7a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/335.a13f339e97893437.js +0 -94
- package/apps/pwa/out/_next/static/chunks/3394.646348f34b2500be.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3443.6276909b53d72d53.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3495.abd4254d808bf2dc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3721.ba5b82db0d9a5572.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3857-5985236d9307f676.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3919.9ff7dc25afefee33.js +0 -74
- package/apps/pwa/out/_next/static/chunks/3998.bd9a69d9b50e9bd2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4003.ab6db1dc266576c9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4144-fad095b48a1862ee.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4216.e8ac94b9fe6b96ab.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4327.c44dc2b55cb43ee5.js +0 -34
- package/apps/pwa/out/_next/static/chunks/4401.94257d497b6d26bc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4559.17ca2cdbaa8d414f.js +0 -115
- package/apps/pwa/out/_next/static/chunks/4565-3d7e3fdeb43686a8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4706.97e734bd4c9816ca.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4795-f372490b178583fc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4982.6e44352f751b9a1b.js +0 -77
- package/apps/pwa/out/_next/static/chunks/5342.49c5c5a530e62b50.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5360-08a80abdf3a4da99.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5431.66c58ab1327ee0b7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5633-d6852385000738ef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5717.bcfcb96e701b82e0.js +0 -90
- package/apps/pwa/out/_next/static/chunks/5755-30d102586789bf7f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5957.3f1a4012388dd962.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-5b9e8a45a5b0c823.js +0 -1
- package/apps/pwa/out/_next/static/chunks/602.9bf02ac8e9502598.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6020.3a1b347e2d2dfacc.js +0 -8
- package/apps/pwa/out/_next/static/chunks/6375.fce1d174ba6d0324.js +0 -64
- package/apps/pwa/out/_next/static/chunks/6546.92a828aef441271f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/658.957fdb0a8c6c6c8e.js +0 -287
- package/apps/pwa/out/_next/static/chunks/6706.323590aabb8b1293.js +0 -226
- package/apps/pwa/out/_next/static/chunks/6747.b67947f293a4c02d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6868.000b24a3045c5574.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6946.bba74e9c96050f30.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7033.a558f1b2f6912a53.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7065.46029c26c53eac50.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7087.6fae577d09249bc1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/722.de8d9801e0f6d414.js +0 -41
- package/apps/pwa/out/_next/static/chunks/726.83532d69452ee24b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7497.57981e9d7a75a52a.js +0 -24
- package/apps/pwa/out/_next/static/chunks/7672.4af122ad892117c7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/771.6ab889a233e7dae7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8096.f7da0776cfde6300.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8401-35e94e9091f5eb8d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8769.815ca69879f608b4.js +0 -104
- package/apps/pwa/out/_next/static/chunks/8857-aa1cc4ac90af4d40.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8f835b4f.048eeb02299e3e12.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9068.5f2764fccf86c1b2.js +0 -93
- package/apps/pwa/out/_next/static/chunks/9103-60fbef33e66f0a46.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9247-55bcfbb0e8057f59.js +0 -1
- package/apps/pwa/out/_next/static/chunks/935.4ddaa99fc1d1126c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9451.ebfb0dcc9c02250f.js +0 -23
- package/apps/pwa/out/_next/static/chunks/9559.e648369b1d57cf9a.js +0 -62
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-88459e0903da2803.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-d85c43f46f58716b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-4e645600579ae952.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-87edbfd19117823b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-7f0c2b7801ac6a54.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-bf1f030a9d1adffd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-cbaf66b1e1804adf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-9f5610caaa06178c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-ec8f3575e339de74.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-86d753e460f1a989.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-4166a7d51cf0a57d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-2a19c5cd3b562580.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-b6a09cde2e07521a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-f7eae1518ca0c353.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-456a8573ad811381.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-8462d6a543cbb2d9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-2974cdfb87c0f9c9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-f927ffa25f3c86d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-abd86cd832aaa011.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-79daf68f6256adaa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8c3f168fc3f91939.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-786fb4504d0f2fa9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-75209f14597dcb8f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-bf30affe3a85af7e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1187a44b7515434b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-3d0910185a66ed54.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-f401ee1dd5a26e25.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-5dfe7604fd842784.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-76cf838050a8d681.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-7ec8e289b4c14df6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-8c8ac17d52be2a79.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-d359ab048d63a9d9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-b8b345c880bf78d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-f151cc10ad026940.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-9875f103057da433.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-9789328af9a9aa62.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-bf495a1e284f4e00.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-a263d94bca4be22a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-3962eab8ec58d16e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-9abcfac07e02a542.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-bee49f927c333abb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-bac2d56dcb02d955.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-937cbcc13176295c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d09495594f7f480d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-69cb93d1a2e57e0e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-72ec543928d67c3e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-82906eeda0186a7b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/f6231284-45726550aba08fca.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-f2b4016b9b981bc8.js +0 -1
- package/apps/pwa/out/_next/static/css/f2bd5257e6af88d8.css +0 -1
- /package/apps/pwa/out/_next/static/{aBi6EyMtLsrl7J8eH4kF7 → rfkMm4movqkqMebBrCXiR}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IMAGE-ONE-SOURCE (대표 2026-10-09 «이미지도 공통 사용·기준값 관리 — 멀티 서버가 같은 곳을 보게»).
|
|
3
|
+
*
|
|
4
|
+
* `--substrate pod` 발사는 지금 «이 트리 HEAD ≠ 노드 이미지»면 그 자리에서 굽는다 — 부모 여럿이 같은 커밋을 동시에 굽고,
|
|
5
|
+
* 노드마다 `:local` 판이 갈렸다. 운영 config `harness.imageCommit`(커밋 12자리 이상)이 있으면 발사는 굽지 않고
|
|
6
|
+
* 레지스트리의 «기준 이미지»(커밋 태그)를 당긴다. 없으면 종전 경로(HEAD 비교·굽기) 그대로다.
|
|
7
|
+
*
|
|
8
|
+
* ⛔ 레지스트리 주소·태그 규칙은 docker/harness/build.sh 의 것이다(새 규칙을 만들지 않는다):
|
|
9
|
+
* REGISTRY="${ELANOUS_BUILD_REGISTRY:-k3d-elanous-registry:${ELANOUS_FLEET_REGISTRY_PORT:-5050}}" · TAG="${COMMIT:0:12}".
|
|
10
|
+
* 풀 멤버가 `#k3d-레지스트리:포트` 를 명시했으면 그 멤버는 그 주소다(syncPoolImages 가 build.sh 에 넘기는 것과 같다).
|
|
11
|
+
* 조회·Job ref·오류 문구는 전부 «멤버마다 정해진 그 한 주소»를 쓴다.
|
|
12
|
+
* 레지스트리가 있는 풀 멤버는 원격이면 SSH, 로컬이면 이 기계에서 기준 태그를 찾는다.
|
|
13
|
+
* 레지스트리가 없는 멤버(node-c 등)와 조회할 수 없는 로컬 레지스트리는 종전 경로로 간다 — 레지스트리 미러는 범위 밖.
|
|
14
|
+
* 이미지는 표준판(elanous-harness)만 — 이 발사 경로는 lite 를 쓰지 않는다.
|
|
15
|
+
*/
|
|
16
|
+
import { spawnSync } from 'node:child_process';
|
|
17
|
+
import { debug } from '../../debug/log.js';
|
|
18
|
+
import { memberHasRegistry, registryImageRef, type RemoteRun } from './pod-pool.js';
|
|
19
|
+
|
|
20
|
+
const POD_IMAGE = 'elanous-harness';
|
|
21
|
+
|
|
22
|
+
export type PodImageSource =
|
|
23
|
+
| { readonly kind: 'pinned'; readonly ref: string; readonly commit: string; readonly registry: string }
|
|
24
|
+
| { readonly kind: 'head' };
|
|
25
|
+
|
|
26
|
+
export type PodImageSourceOutcome = 'pinned-ready' | 'pinned-waited' | 'pinned-missing' | 'head';
|
|
27
|
+
|
|
28
|
+
/** 기준 이미지가 레지스트리에 올라오기를 기다리는 기본 한도(초). */
|
|
29
|
+
export const POD_IMAGE_PIN_WAIT_SEC = 600;
|
|
30
|
+
const POD_IMAGE_PIN_POLL_MS = 15_000;
|
|
31
|
+
|
|
32
|
+
const COMMIT = /^[0-9a-f]{12,40}$/u;
|
|
33
|
+
|
|
34
|
+
/** `harness.imageCommit` 이 유효한 값인가(커밋 hex 12~40자리). */
|
|
35
|
+
export function isPodImageCommit(value: unknown): value is string {
|
|
36
|
+
return typeof value === 'string' && COMMIT.test(value.trim().toLowerCase());
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** build.sh:274 와 같은 규칙 — `ELANOUS_BUILD_REGISTRY` 다음 `k3d-elanous-registry:${ELANOUS_FLEET_REGISTRY_PORT:-5050}`. */
|
|
40
|
+
export function podImageRegistry(env: NodeJS.ProcessEnv = process.env): string {
|
|
41
|
+
// bash `${X:-d}` 는 빈 문자열도 «없음»으로 본다 — `||` 로 맞춘다.
|
|
42
|
+
return env.ELANOUS_BUILD_REGISTRY || `k3d-elanous-registry:${env.ELANOUS_FLEET_REGISTRY_PORT || '5050'}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** build.sh:276 — 태그 = 커밋 앞 12자리. */
|
|
46
|
+
export function podImageRef(registry: string, commit: string): string {
|
|
47
|
+
return `${registry}/${POD_IMAGE}:${commit.slice(0, 12)}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function resolvePodImageSource(
|
|
51
|
+
cfg: { harness?: { imageCommit?: unknown } } | null | undefined,
|
|
52
|
+
deps: { env?: NodeJS.ProcessEnv } = {},
|
|
53
|
+
): PodImageSource {
|
|
54
|
+
const raw = cfg?.harness?.imageCommit;
|
|
55
|
+
if (!isPodImageCommit(raw)) return { kind: 'head' };
|
|
56
|
+
const commit = raw.trim().toLowerCase();
|
|
57
|
+
const registry = podImageRegistry(deps.env ?? process.env);
|
|
58
|
+
return { kind: 'pinned', ref: podImageRef(registry, commit), commit, registry };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function podImageMissingMessage(ref: string): string {
|
|
62
|
+
return `기준 이미지 ${ref} 가 레지스트리에 없다 — 레일이 굽기 전이다`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface PodImageMember { readonly context: string; readonly sshHost?: string; readonly registry?: string }
|
|
66
|
+
|
|
67
|
+
/** 이 멤버가 기준 이미지를 찾을 레지스트리 — 멤버 명시값, 없으면 build.sh 규칙. */
|
|
68
|
+
export function memberPinRegistry(member: PodImageMember, source: Extract<PodImageSource, { kind: 'pinned' }>): string {
|
|
69
|
+
return member.registry ?? source.registry;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** 실제 조회 — 원격은 pod-pool 의 SSH 판정, 로컬은 이 기계의 docker/curl 로 조회한다. */
|
|
73
|
+
export function podPoolImageLookup(run?: RemoteRun, localRun: (cmd: string, args: readonly string[]) => { status: number | null; stdout: string } = (cmd, args) => {
|
|
74
|
+
const r = spawnSync(cmd, [...args], { encoding: 'utf8', timeout: 10_000 });
|
|
75
|
+
return { status: r.status, stdout: r.stdout ?? '' };
|
|
76
|
+
}): {
|
|
77
|
+
hasRegistry: (member: PodImageMember & { context: string }, registry: string) => boolean;
|
|
78
|
+
lookupTag: (member: PodImageMember & { context: string }, registry: string, commit: string) => string | null;
|
|
79
|
+
} {
|
|
80
|
+
return {
|
|
81
|
+
hasRegistry: (member, registry) => {
|
|
82
|
+
if (member.sshHost) return memberHasRegistry({ capacity: 0, k3dCluster: '', ...member, registry }, run);
|
|
83
|
+
try { return localRun('docker', ['inspect', registry.split(':')[0]!]).status === 0; }
|
|
84
|
+
catch { return false; }
|
|
85
|
+
},
|
|
86
|
+
lookupTag: (member, registry, commit) => {
|
|
87
|
+
if (member.sshHost) return registryImageRef({ capacity: 0, k3dCluster: '', ...member, registry }, commit, run, `${POD_IMAGE}:local`);
|
|
88
|
+
const port = registry.slice(registry.lastIndexOf(':') + 1);
|
|
89
|
+
const tag = commit.slice(0, 12);
|
|
90
|
+
try {
|
|
91
|
+
const r = localRun('curl', ['-fsS', '--max-time', '5', `http://localhost:${port}/v2/${POD_IMAGE}/tags/list`]);
|
|
92
|
+
if (r.status !== 0) return null;
|
|
93
|
+
const tags = (JSON.parse(r.stdout) as { tags?: string[] }).tags;
|
|
94
|
+
return Array.isArray(tags) && tags.includes(tag) ? podImageRef(registry, commit) : null;
|
|
95
|
+
} catch { return null; }
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface PreparePodImageDeps<M extends PodImageMember, H> {
|
|
101
|
+
readonly cfg: { harness?: { imageCommit?: unknown } } | null | undefined;
|
|
102
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
103
|
+
/** 풀 멤버(풀이 없으면 빈 배열 — 이 기계의 컨텍스트 하나). */
|
|
104
|
+
readonly members: readonly M[];
|
|
105
|
+
/** 종전 경로(HEAD 비교·굽기·노드 동기화). 기준값이 없으면 전 멤버로, 있으면 «기준 이미지를 못 쓰는» 멤버로만 부른다. */
|
|
106
|
+
readonly headPath: (members: readonly M[]) => Promise<H>;
|
|
107
|
+
/** 이 멤버의 노드에 그 레지스트리가 있나. */
|
|
108
|
+
readonly hasRegistry: (member: M, registry: string) => boolean;
|
|
109
|
+
/** 이 멤버의 그 레지스트리에 그 커밋 태그가 있으면 Pod 가 당길 ref, 없으면 null. */
|
|
110
|
+
readonly lookupTag: (member: M, registry: string, commit: string) => string | null;
|
|
111
|
+
readonly maxWaitMs?: number;
|
|
112
|
+
readonly pollMs?: number;
|
|
113
|
+
readonly sleep?: (ms: number) => Promise<void>;
|
|
114
|
+
readonly now?: () => number;
|
|
115
|
+
readonly onWait?: (pending: readonly M[], waitedMs: number) => void;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface PreparedPodImage<M extends PodImageMember, H> {
|
|
119
|
+
readonly source: PodImageSource;
|
|
120
|
+
readonly outcome: PodImageSourceOutcome;
|
|
121
|
+
/** 기준 이미지를 당기는 멤버 — `imageRef` 가 Job 이미지다. */
|
|
122
|
+
readonly pinned: ReadonlyArray<M & { imageRef: string }>;
|
|
123
|
+
/** 레지스트리는 있는데 한도 안에 태그가 안 올라온 멤버와 그 멤버가 찾던 ref. */
|
|
124
|
+
readonly missing: ReadonlyArray<{ member: M; ref: string }>;
|
|
125
|
+
/** 종전 경로를 탄 멤버(레지스트리 없음 · 로컬 태그 조회 실패). */
|
|
126
|
+
readonly unpinned: readonly M[];
|
|
127
|
+
/** 종전 경로의 결과 — 부르지 않았으면 null. */
|
|
128
|
+
readonly head: H | null;
|
|
129
|
+
readonly waitedMs: number;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 발사 이미지를 준비한다. 기준값이 없으면 `headPath(members)` 하나만 부른다(종전 동작 그대로).
|
|
134
|
+
* 기준값이 있으면: 레지스트리가 있는 멤버는 태그를 기다려 당기고(굽지 않는다), 나머지 멤버만 종전 경로로 보낸다.
|
|
135
|
+
* 원격 레지스트리 멤버 «전부» 태그가 끝내 없으면 outcome `pinned-missing` — 종전 경로도 부르지 않는다(호출부가 exit 2).
|
|
136
|
+
*/
|
|
137
|
+
export async function preparePodImage<M extends PodImageMember, H>(deps: PreparePodImageDeps<M, H>): Promise<PreparedPodImage<M, H>> {
|
|
138
|
+
const source = resolvePodImageSource(deps.cfg, { env: deps.env });
|
|
139
|
+
if (source.kind === 'head') {
|
|
140
|
+
return { source, outcome: 'head', pinned: [], missing: [], unpinned: deps.members, head: await deps.headPath(deps.members), waitedMs: 0 };
|
|
141
|
+
}
|
|
142
|
+
const localRefs = new Map<string, string>();
|
|
143
|
+
const registryMembers = deps.members.filter((m) => {
|
|
144
|
+
const registry = memberPinRegistry(m, source);
|
|
145
|
+
if (!deps.hasRegistry(m, registry)) return false;
|
|
146
|
+
if (m.sshHost) return true;
|
|
147
|
+
// A local registry without the requested tag (or an unreadable one) can still use the old build path.
|
|
148
|
+
const ref = deps.lookupTag(m, registry, source.commit);
|
|
149
|
+
if (!ref) return false;
|
|
150
|
+
localRefs.set(m.context, ref);
|
|
151
|
+
return true;
|
|
152
|
+
});
|
|
153
|
+
const unpinned = deps.members.filter((m) => !registryMembers.includes(m));
|
|
154
|
+
if (registryMembers.length === 0) {
|
|
155
|
+
// 기준 이미지를 당길 멤버가 없다(풀 없음 · 레지스트리 없는 멤버뿐) — 종전 경로.
|
|
156
|
+
return { source, outcome: 'head', pinned: [], missing: [], unpinned, head: await deps.headPath(unpinned), waitedMs: 0 };
|
|
157
|
+
}
|
|
158
|
+
const now = deps.now ?? Date.now;
|
|
159
|
+
const sleep = deps.sleep ?? ((ms: number) => new Promise<void>((done) => setTimeout(done, ms)));
|
|
160
|
+
const maxWaitMs = Math.max(0, deps.maxWaitMs ?? POD_IMAGE_PIN_WAIT_SEC * 1000);
|
|
161
|
+
const pollMs = Math.max(1, deps.pollMs ?? POD_IMAGE_PIN_POLL_MS);
|
|
162
|
+
const found = new Map<string, string>();
|
|
163
|
+
const t0 = now();
|
|
164
|
+
let pending = registryMembers;
|
|
165
|
+
let slept = false;
|
|
166
|
+
for (;;) {
|
|
167
|
+
pending = pending.filter((m) => {
|
|
168
|
+
const ref = localRefs.get(m.context) ?? deps.lookupTag(m, memberPinRegistry(m, source), source.commit);
|
|
169
|
+
if (ref) found.set(m.context, ref);
|
|
170
|
+
return !ref;
|
|
171
|
+
});
|
|
172
|
+
const waited = now() - t0;
|
|
173
|
+
if (pending.length === 0 || waited >= maxWaitMs) break;
|
|
174
|
+
deps.onWait?.(pending, waited);
|
|
175
|
+
slept = true;
|
|
176
|
+
await sleep(Math.min(pollMs, maxWaitMs - waited));
|
|
177
|
+
}
|
|
178
|
+
const waitedMs = now() - t0;
|
|
179
|
+
const pinned = registryMembers.filter((m) => found.has(m.context)).map((m) => ({ ...m, imageRef: found.get(m.context)! }));
|
|
180
|
+
const missing = pending.map((member) => ({ member, ref: podImageRef(memberPinRegistry(member, source), source.commit) }));
|
|
181
|
+
if (pinned.length === 0) {
|
|
182
|
+
return { source, outcome: 'pinned-missing', pinned, missing, unpinned, head: null, waitedMs };
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
source,
|
|
186
|
+
outcome: slept ? 'pinned-waited' : 'pinned-ready',
|
|
187
|
+
pinned,
|
|
188
|
+
missing,
|
|
189
|
+
unpinned,
|
|
190
|
+
head: unpinned.length > 0 ? await deps.headPath(unpinned) : null,
|
|
191
|
+
waitedMs,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* 기준값 경로의 `image-source` 사건 — 발사 호출부(index.ts)가 부르는 단 하나의 기록처.
|
|
197
|
+
* 이 기계의 로컬 멤버(sshHost 없음)가 기준 이미지를 당겼으면 `local: true` 를 싣는다.
|
|
198
|
+
*/
|
|
199
|
+
export function logPinnedImageSource<M extends PodImageMember, H>(
|
|
200
|
+
prepared: PreparedPodImage<M, H>,
|
|
201
|
+
ctx: { readonly ref: string; readonly headCommit: string | null },
|
|
202
|
+
): void {
|
|
203
|
+
if (prepared.source.kind !== 'pinned') return;
|
|
204
|
+
const local = prepared.pinned.some((m) => !m.sshHost);
|
|
205
|
+
debug.log('self-implement.pod', 'image-source', {
|
|
206
|
+
kind: prepared.source.kind, ref: ctx.ref, pinRef: prepared.source.ref, headCommit: ctx.headCommit, waitedMs: prepared.waitedMs, outcome: prepared.outcome,
|
|
207
|
+
...(local ? { local: true } : {}),
|
|
208
|
+
pinned: prepared.pinned.map((m) => ({ context: m.context, imageRef: m.imageRef })),
|
|
209
|
+
missing: prepared.missing.map((x) => ({ context: x.member.context, ref: x.ref })),
|
|
210
|
+
unpinned: prepared.unpinned.map((m) => m.context),
|
|
211
|
+
});
|
|
212
|
+
}
|
|
@@ -8,7 +8,14 @@ import { dlopen } from 'bun:ffi';
|
|
|
8
8
|
import { hostLeaseBaseDir, type PendingJobIdentity } from '../../pod-lease/host-lease.js';
|
|
9
9
|
|
|
10
10
|
/** The harness Pod's container requests (self-implement-pod Job manifest). Lives here so admission reuses the real request. */
|
|
11
|
-
|
|
11
|
+
// POD-CPU-REQUEST-RIGHTSIZE(10-09 · OP 실측): 하니스 Pod 실사용은 대부분 100m 미만(LLM 응답 대기)인데 요청 1 CPU 를
|
|
12
|
+
// 잡아 node-b 32코어가 «요청 31/32»로 막혔다(실사용 4.6코어 · 14%). 요청은 실사용 근처로, 한도(4)는 그대로 — 몰릴 때는 한도까지 쓴다.
|
|
13
|
+
// 게이트·pod-command 기본은 pod-command-job.ts 의 POD_COMMAND_CPU_REQUEST_DEFAULT(1)로 따로 둔다(테스트 조각은 CPU 를 실제로 쓴다).
|
|
14
|
+
export const POD_CHILD_REQUESTS = { cpu: '250m', memory: '6Gi' } as const;
|
|
15
|
+
/** Finished harness and command Jobs release their Pod slots after 30 minutes. */
|
|
16
|
+
export const POD_JOB_TTL_AFTER_FINISHED_SECONDS = 1800;
|
|
17
|
+
/** 게이트가 돌 때 하니스 «한 자리»로 셈하는 CPU — 요청(250m)보다 보수적으로 1 코어를 잡아 게이트 조각의 CPU 를 지킨다(GATE-RESERVE-AUTO 셈은 그대로). */
|
|
18
|
+
export const HARNESS_SLOT_CPU_BESIDE_GATE = '1';
|
|
12
19
|
/** POD-ADMIT-BY-USAGE — observed usage is scaled by this before it reserves memory. */
|
|
13
20
|
const POD_USAGE_HEADROOM = 1.5;
|
|
14
21
|
/** POD-ADMIT-BY-USAGE — a node keeps at least this share of allocatable memory free (real usage and bookkeeping). */
|
|
@@ -207,6 +214,7 @@ function runningLimit(spec: Record<string, unknown>): number | null {
|
|
|
207
214
|
/** A short-lived Pod proves cluster DNS from inside the same namespace as harness Jobs. Image pull failures are not DNS failures. */
|
|
208
215
|
export type PoolDnsProbe = 'ready' | 'dns' | 'image' | 'unknown';
|
|
209
216
|
const DNS_CACHE_MS = 10 * 60_000;
|
|
217
|
+
const DNS_STALE_OK_MS = 30 * 60_000;
|
|
210
218
|
const DNS_LOCK_WAIT_MS = 6 * 60_000;
|
|
211
219
|
// flock is released by the kernel on process exit, including a crash before any cache write.
|
|
212
220
|
// The lock file is never unlinked: unlinking it would let waiters lock different inodes.
|
|
@@ -232,6 +240,17 @@ export function probePoolDns(context: string, kubectl: PoolKubectl = leaseKubect
|
|
|
232
240
|
return record?.result === 'ready' && typeof record.at === 'number' &&
|
|
233
241
|
now() >= record.at && now() - record.at < DNS_CACHE_MS;
|
|
234
242
|
};
|
|
243
|
+
const withStaleReady = (result: PoolDnsProbe): PoolDnsProbe => {
|
|
244
|
+
if (result !== 'unknown') return result;
|
|
245
|
+
const record = readRecord(cache);
|
|
246
|
+
const readyAgeMs = typeof record?.at === 'number' ? now() - record.at : NaN;
|
|
247
|
+
if (record?.result === 'ready' &&
|
|
248
|
+
Number.isFinite(readyAgeMs) && readyAgeMs >= 0 && readyAgeMs < DNS_STALE_OK_MS) {
|
|
249
|
+
debug.log('pod.lease', 'dns-probe-stale-ready', { context, readyAgeMs });
|
|
250
|
+
return 'ready';
|
|
251
|
+
}
|
|
252
|
+
return result;
|
|
253
|
+
};
|
|
235
254
|
if (cached()) return 'ready';
|
|
236
255
|
const previous = readRecord(outcome);
|
|
237
256
|
try { mkdirSync(dir, { recursive: true, mode: 0o700 }); } catch { return 'unknown'; }
|
|
@@ -251,31 +270,34 @@ export function probePoolDns(context: string, kubectl: PoolKubectl = leaseKubect
|
|
|
251
270
|
while (!held) {
|
|
252
271
|
if (cached()) return 'ready';
|
|
253
272
|
const shared = sharedFailure();
|
|
254
|
-
if (shared) return shared;
|
|
273
|
+
if (shared) return withStaleReady(shared);
|
|
255
274
|
if (dnsFlock(fd, 2 | 4) === 0) { held = true; break; } // LOCK_EX | LOCK_NB
|
|
256
275
|
if (Date.now() >= deadline) return 'unknown';
|
|
257
276
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
|
|
258
277
|
}
|
|
259
278
|
if (cached()) return 'ready';
|
|
260
279
|
const shared = sharedFailure();
|
|
261
|
-
if (shared) return shared;
|
|
262
|
-
const result = runPoolDnsProbe(context, kubectl);
|
|
280
|
+
if (shared) return withStaleReady(shared);
|
|
281
|
+
const result = runPoolDnsProbe(context, kubectl, now);
|
|
263
282
|
const destination = result === 'ready' ? cache : outcome;
|
|
264
283
|
const temp = `${destination}.${randomUUID()}.tmp`;
|
|
265
284
|
try {
|
|
266
285
|
writeFileSync(temp, JSON.stringify({ result, at: now(), generation: randomUUID() }), { mode: 0o600 });
|
|
267
286
|
renameSync(temp, destination);
|
|
268
287
|
} catch { try { rmSync(temp, { force: true }); } catch { /* best-effort cleanup */ } }
|
|
269
|
-
return result;
|
|
288
|
+
return withStaleReady(result);
|
|
270
289
|
} finally {
|
|
271
290
|
if (held) dnsFlock(fd, 8); // LOCK_UN
|
|
272
291
|
closeSync(fd);
|
|
273
292
|
}
|
|
274
293
|
}
|
|
275
294
|
|
|
276
|
-
|
|
295
|
+
const DNS_PROBE_READY = /\bName:\s*kubernetes\.default\.svc\.cluster\.local\b/u;
|
|
296
|
+
|
|
297
|
+
function runPoolDnsProbe(context: string, kubectl: PoolKubectl, now: () => number): PoolDnsProbe {
|
|
277
298
|
const name = `elanous-dns-${randomUUID().slice(0, 12)}`;
|
|
278
299
|
const base = ['--context', context, '--request-timeout=10s'];
|
|
300
|
+
let cleanup = false;
|
|
279
301
|
try {
|
|
280
302
|
const core = kubectl([...base, '-n', 'kube-system', 'get', 'deployment', 'coredns', '-o', 'json']);
|
|
281
303
|
if (core.status !== 0) return 'unknown';
|
|
@@ -283,12 +305,61 @@ function runPoolDnsProbe(context: string, kubectl: PoolKubectl): PoolDnsProbe {
|
|
|
283
305
|
const spec = object(deployment?.spec);
|
|
284
306
|
const status = object(deployment?.status);
|
|
285
307
|
if (typeof spec?.replicas !== 'number' || spec.replicas < 1 || status?.readyReplicas !== spec.replicas) return 'dns';
|
|
308
|
+
const listed = kubectl([...base, '-n', 'elanous-test', 'get', 'pods', '-l', 'elanous.probe=dns', '-o', 'json']);
|
|
309
|
+
if (listed.status !== 0) return 'unknown';
|
|
310
|
+
const listedPods = items(JSON.parse(listed.stdout));
|
|
311
|
+
if (!listedPods) return 'unknown';
|
|
312
|
+
const probes = listedPods.filter((pod) => {
|
|
313
|
+
const metadata = object(pod.metadata);
|
|
314
|
+
return object(metadata?.labels)?.['elanous.probe'] === 'dns' &&
|
|
315
|
+
(metadata?.namespace === undefined || metadata.namespace === 'elanous-test');
|
|
316
|
+
});
|
|
317
|
+
let reaped = 0;
|
|
318
|
+
for (const probe of probes) {
|
|
319
|
+
const metadata = object(probe.metadata);
|
|
320
|
+
const name = metadata?.name;
|
|
321
|
+
const deletedAt = metadata?.deletionTimestamp;
|
|
322
|
+
if (typeof name !== 'string' || !/^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/.test(name) ||
|
|
323
|
+
typeof deletedAt !== 'string' || !Number.isFinite(Date.parse(deletedAt)) ||
|
|
324
|
+
now() - Date.parse(deletedAt) <= 60_000) continue;
|
|
325
|
+
try {
|
|
326
|
+
if (kubectl([...base, '-n', 'elanous-test', 'delete', 'pods', '-l', 'elanous.probe=dns', `--field-selector=metadata.name=${name}`, '--grace-period=0', '--force', '--ignore-not-found=true', '--wait=false']).status === 0) reaped++;
|
|
327
|
+
} catch { /* Reaping is best-effort; the crowded reading still prevents a new probe. */ }
|
|
328
|
+
}
|
|
329
|
+
if (reaped) debug.log('pod.lease', 'dns-probe-stale-reaped', { context, count: reaped });
|
|
330
|
+
// 🩸 10-09 22:1x: node-b had 20 Completed probes left over from older releases (and 7 Terminating). Counting every
|
|
331
|
+
// labelled Pod would make the node «crowded» forever and admission would stay 0 — only live probes are crowding.
|
|
332
|
+
// Finished probes are removed here so older leftovers drain too.
|
|
333
|
+
let finished = 0;
|
|
334
|
+
const live = probes.filter((probe) => {
|
|
335
|
+
const metadata = object(probe.metadata);
|
|
336
|
+
if (metadata?.deletionTimestamp !== undefined) return false;
|
|
337
|
+
const phase = object(probe.status)?.phase;
|
|
338
|
+
if (phase !== 'Succeeded' && phase !== 'Failed') return true;
|
|
339
|
+
const name = metadata?.name;
|
|
340
|
+
if (typeof name === 'string' && /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/.test(name)) {
|
|
341
|
+
try {
|
|
342
|
+
if (kubectl([...base, '-n', 'elanous-test', 'delete', 'pods', '-l', 'elanous.probe=dns', `--field-selector=metadata.name=${name}`, '--ignore-not-found=true', '--wait=false']).status === 0) finished++;
|
|
343
|
+
} catch { /* best-effort: a finished probe never crowds */ }
|
|
344
|
+
}
|
|
345
|
+
return false;
|
|
346
|
+
});
|
|
347
|
+
if (finished) debug.log('pod.lease', 'dns-probe-finished-reaped', { context, count: finished });
|
|
348
|
+
if (live.length >= 3) {
|
|
349
|
+
debug.log('pod.lease', 'dns-probe-skipped-crowded', { context, existing: live.length });
|
|
350
|
+
return 'unknown';
|
|
351
|
+
}
|
|
352
|
+
cleanup = true;
|
|
286
353
|
const manifest = JSON.stringify({ apiVersion: 'v1', kind: 'Pod', metadata: { name, namespace: 'elanous-test', labels: { 'elanous.probe': 'dns' } }, spec: {
|
|
287
|
-
restartPolicy: 'Never', activeDeadlineSeconds:
|
|
288
|
-
|
|
354
|
+
restartPolicy: 'Never', activeDeadlineSeconds: 120, automountServiceAccountToken: false,
|
|
355
|
+
// 🩸 10-09 18:2x: 노드 CPU «요청» 100%(32/32) 이면 10m 탐침도 못 떠 측정이 «dns probe unknown» → 승인 추천 null → 전 발사 정지(교착).
|
|
356
|
+
// 탐침은 CPU 요청 0(명시 · 안 쓰면 k8s 가 한도 100m 를 요청으로 복사)으로 «꽉 찬 노드에도 뜨게» 한다.
|
|
357
|
+
containers: [{ name: 'dns', image: 'busybox:1.36', command: ['nslookup', 'kubernetes.default.svc.cluster.local'], resources: { requests: { cpu: '0', memory: '16Mi' }, limits: { cpu: '100m', memory: '64Mi' } } }],
|
|
289
358
|
} });
|
|
290
359
|
if (kubectl([...base, 'create', '-f', '-'], manifest).status !== 0) return 'unknown';
|
|
291
|
-
|
|
360
|
+
// 🩸 10-09 19:3x: 부하 104 노드에선 «배치됨»(PodScheduled=True) 뒤 컨테이너 생성만 30초를 넘겼다 → 30초 wait 가 Pending 을 보고 unknown.
|
|
361
|
+
// 기다림 90초 · Pod 기한 120초. 성공은 DNS_CACHE_MS 동안 공유 캐시라 매 측정이 이 시간을 물지 않는다.
|
|
362
|
+
const waited = kubectl([...base, '-n', 'elanous-test', 'wait', '--for=jsonpath={.status.phase}=Succeeded', `pod/${name}`, '--timeout=90s']);
|
|
292
363
|
const pod = kubectl([...base, '-n', 'elanous-test', 'get', `pod/${name}`, '-o', 'json']);
|
|
293
364
|
if (pod.status !== 0) return 'unknown';
|
|
294
365
|
const state = object(JSON.parse(pod.stdout));
|
|
@@ -296,14 +367,24 @@ function runPoolDnsProbe(context: string, kubectl: PoolKubectl): PoolDnsProbe {
|
|
|
296
367
|
const statuses = object(state?.status)?.containerStatuses;
|
|
297
368
|
if (Array.isArray(statuses) && statuses.some((c) =>
|
|
298
369
|
['ErrImagePull', 'ImagePullBackOff', 'InvalidImageName'].includes(String(object(object(object(c)?.state)?.waiting)?.reason)))) return 'image';
|
|
370
|
+
// 🩸 10-09 18:5x: 부하 걸린 노드(load 78)에선 kubelet 이 «끝난» 탐침의 phase 를 30초 넘게 Running 으로 둔다
|
|
371
|
+
// (nslookup 이 답을 찍은 뒤에도 · `timeout 8 nslookup` 도 46초 Running). phase 만 보면 측정이 «unknown» → 승인 0.
|
|
372
|
+
// Running 이면 로그를 읽어 해석 성공(이름·주소 줄)이 이미 찍혔는지 본다 — 찍혔으면 DNS 는 된다.
|
|
373
|
+
if (phase === 'Running') {
|
|
374
|
+
const early = kubectl([...base, '-n', 'elanous-test', 'logs', `pod/${name}`]);
|
|
375
|
+
if (early.status === 0 && DNS_PROBE_READY.test(early.stdout) && /\bAddress(?:es)?:\s*\S+/u.test(early.stdout)) {
|
|
376
|
+
debug.log('pod.lease', 'dns-probe-ready-by-log', { context, phase });
|
|
377
|
+
return 'ready';
|
|
378
|
+
}
|
|
379
|
+
}
|
|
299
380
|
if (phase !== 'Succeeded' && phase !== 'Failed') return 'unknown';
|
|
300
381
|
if (waited.status !== 0 && phase !== 'Failed') return 'unknown';
|
|
301
382
|
const log = kubectl([...base, '-n', 'elanous-test', 'logs', `pod/${name}`]);
|
|
302
383
|
if (log.status !== 0) return 'unknown';
|
|
303
384
|
if (phase === 'Failed') return /can't resolve|server can't find|connection timed out|no servers could be reached|NXDOMAIN|SERVFAIL/iu.test(log.stdout) ? 'dns' : 'unknown';
|
|
304
|
-
return
|
|
385
|
+
return DNS_PROBE_READY.test(log.stdout) && /\bAddress(?:es)?:\s*\S+/u.test(log.stdout) ? 'ready' : 'dns';
|
|
305
386
|
} catch { return 'unknown'; }
|
|
306
|
-
finally { try { kubectl([...base, '-n', 'elanous-test', 'delete', `pod/${name}`, '--ignore-not-found=true', '--wait=false']); } catch { /* best-effort cleanup */ } }
|
|
387
|
+
finally { if (cleanup) { try { kubectl([...base, '-n', 'elanous-test', 'delete', `pod/${name}`, '--ignore-not-found=true', '--wait=false']); } catch { /* best-effort cleanup */ } } }
|
|
307
388
|
}
|
|
308
389
|
|
|
309
390
|
/** Each cluster reads resource reservations; DNS requires a bounded disposable Pod. */
|
|
@@ -534,7 +615,7 @@ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kube
|
|
|
534
615
|
*/
|
|
535
616
|
export function harnessCpuSlotsBesideGate(measure: PoolLeaseMeasure): number | null | undefined {
|
|
536
617
|
if (!measure.members.some((m) => (m.gateJobs ?? 0) > 0)) return undefined;
|
|
537
|
-
const perPod = quantity(
|
|
618
|
+
const perPod = quantity(HARNESS_SLOT_CPU_BESIDE_GATE, 'cpu')!;
|
|
538
619
|
let slots = 0;
|
|
539
620
|
for (const m of measure.members) {
|
|
540
621
|
if (m.gateJobs == null || m.gateUnboundJobs == null || m.cpuFreeByNodeMillicores == null || m.pendingHarnessCpuMillicores == null) return null;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
2
3
|
import { gunzipSync } from 'node:zlib';
|
|
3
4
|
import { debug } from '../../debug/log.js';
|
|
4
|
-
import {
|
|
5
|
+
import { recordFailureEvent } from '../../self-implement/heal-intake.js';
|
|
6
|
+
import { loadRunLedger, runLedgerDir, runLedgerPath, TRAILING_BOOKKEEPING_EVENTS } from '../../self-implement/run-ledger.js';
|
|
5
7
|
import { ledgerLineToLogEvent } from './pod-ledger-events.js';
|
|
6
8
|
import { defaultPodReemit } from './pod-ledger-prod-sink.js';
|
|
7
9
|
|
|
@@ -91,6 +93,24 @@ export function parsePodLedgerChunks(logs: string): ParseResult {
|
|
|
91
93
|
return error.length ? { error, ledgers } : ledgers;
|
|
92
94
|
}
|
|
93
95
|
|
|
96
|
+
function intakeCollectedFailure(runId: string, dir: string, log: Emit): void {
|
|
97
|
+
let ledger: ReturnType<typeof loadRunLedger>;
|
|
98
|
+
try { ledger = loadRunLedger(runId, dir); }
|
|
99
|
+
catch { return; }
|
|
100
|
+
if (!ledger) return;
|
|
101
|
+
let terminalIndex = ledger.length - 1;
|
|
102
|
+
while (terminalIndex >= 0 && ledger[terminalIndex]?.event !== 'run-status') terminalIndex -= 1;
|
|
103
|
+
const terminal = ledger[terminalIndex];
|
|
104
|
+
if (terminal?.data.runStatus !== 'failed' || !terminal.timestamp || !Number.isFinite(Date.parse(terminal.timestamp))
|
|
105
|
+
|| ledger.slice(terminalIndex + 1).some(entry => !TRAILING_BOOKKEEPING_EVENTS.includes(entry.event as typeof TRAILING_BOOKKEEPING_EVENTS[number]))) return;
|
|
106
|
+
try {
|
|
107
|
+
recordFailureEvent({ source: 'harness-run', kind: 'pod-run', ref: runId,
|
|
108
|
+
summary: `Pod run ${runId} failed`, at: terminal.timestamp }, dirname(dir));
|
|
109
|
+
} catch (error) {
|
|
110
|
+
log('self-implement.pod', 'heal-intake-failed', { runId, reason: error instanceof Error ? error.message : String(error) });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
94
114
|
/** Never replace a host ledger, even if another collector races this one —
|
|
95
115
|
* except a ledger that THIS run's live follower created (`replace`): that one is a partial copy and the final is complete. */
|
|
96
116
|
export function collectPodLedgers(
|
|
@@ -116,13 +136,16 @@ export function collectPodLedgers(
|
|
|
116
136
|
const newLines = Buffer.from(jsonl).subarray(previousBytes).toString('utf8');
|
|
117
137
|
reemitLedgerLines(newLines, runId, previousBytes, emit);
|
|
118
138
|
log('self-implement.pod', 'ledger-collected', { runId, lines: jsonl.split('\n').filter(Boolean).length, bytes: Buffer.byteLength(jsonl) });
|
|
139
|
+
intakeCollectedFailure(runId, dir, log);
|
|
119
140
|
} catch (e) {
|
|
120
141
|
if ((e as NodeJS.ErrnoException).code === 'EEXIST') {
|
|
121
142
|
// A reattached Job may have already delivered this exact complete snapshot.
|
|
122
143
|
// An unrelated host ledger (or a partial copy) must still remain untouched.
|
|
123
144
|
try {
|
|
124
145
|
const existing = readFileSync(runLedgerPath(runId, dir));
|
|
125
|
-
|
|
146
|
+
const identical = existing.equals(Buffer.from(jsonl));
|
|
147
|
+
log('self-implement.pod', identical ? 'ledger-collect-already-complete' : 'ledger-collect-skipped', { runId, reason: 'exists' });
|
|
148
|
+
if (identical) intakeCollectedFailure(runId, dir, log);
|
|
126
149
|
} catch (readError) {
|
|
127
150
|
log('self-implement.pod', 'ledger-collect-incomplete', { runId, reason: readError instanceof Error ? readError.message : String(readError) });
|
|
128
151
|
}
|
|
@@ -23,7 +23,7 @@ import { debug } from '../../debug/log.js';
|
|
|
23
23
|
import { PodLeaseAdmission, type PodLeasePredecessor, type PodLeaseRelease } from '../../pod-lease/admission.js';
|
|
24
24
|
import { predecessorState, type PredecessorState } from '../../pod-lease/dependency-state.js';
|
|
25
25
|
import { HostPoolLease, hostLeaseBaseDir, leaseHasPendingPod } from '../../pod-lease/host-lease.js';
|
|
26
|
-
import { acquireLockSync } from '../../storage/file-lock.js';
|
|
26
|
+
import { acquireLockAsync, acquireLockSync } from '../../storage/file-lock.js';
|
|
27
27
|
import { harnessCpuSlotsBesideGate, measurePoolLease, recommendConcurrency, type PodLeaseMember, type PoolDnsProbe, type PoolLeaseRecommendation } from './pod-lease.js';
|
|
28
28
|
|
|
29
29
|
export interface PodPoolMember {
|
|
@@ -99,6 +99,12 @@ export function registryImageRef(member: PodPoolMember, commit: string | null, r
|
|
|
99
99
|
} catch { return null; }
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
/** IMAGE-ONE-SOURCE: 이 멤버의 노드에 레지스트리 컨테이너가 있나(syncPoolImages 의 판정과 같은 명령). 로컬 멤버는 false. */
|
|
103
|
+
export function memberHasRegistry(member: PodPoolMember, run: RemoteRun = defaultRemoteRun): boolean {
|
|
104
|
+
if (!member.sshHost) return false;
|
|
105
|
+
return run(member.sshHost, `docker inspect ${memberRegistry(member).split(':')[0]} >/dev/null 2>&1`).status === 0;
|
|
106
|
+
}
|
|
107
|
+
|
|
102
108
|
/** 노드 이미지의 스킬 해시 라벨(`elanous.pod-skills`). */
|
|
103
109
|
export function remoteImageSkillsDigest(member: PodPoolMember, image: string, run: RemoteRun = defaultRemoteRun): string | null {
|
|
104
110
|
if (!member.sshHost) return null;
|
|
@@ -276,7 +282,14 @@ export class PodPoolScheduler {
|
|
|
276
282
|
const decision = measured.recommended === null ? measured : { ...measured, recommended: Math.max(0, measured.recommended - others + (transferIsOther ? 1 : 0)) };
|
|
277
283
|
// An unknown reading cannot erase permits already granted: on recovery,
|
|
278
284
|
// the first healthy recommendation must still account for their slots.
|
|
279
|
-
|
|
285
|
+
// GATE-ADMIT-UNMEASURED(10-09 대표 «판 발행 문제없게»): 측정이 «?»(예: 부하 노드의 DNS 탐침)여도 게이트 조각은 하나씩 들어간다 —
|
|
286
|
+
// 게이트가 측정 복구를 기다리며 조각마다 승인 시간 초과를 물지 않게. 실제로 자리가 없으면 스케줄러가 Pending 으로 둔다(잃는 것 없음).
|
|
287
|
+
if (decision.recommended === null) {
|
|
288
|
+
if (!gate) return decision;
|
|
289
|
+
const active = this.gateAdmission.snapshot().active;
|
|
290
|
+
debug.log('pod.pool', 'gate-admit-unmeasured', { active, reason: decision.reason ?? null });
|
|
291
|
+
return { ...decision, recommended: active + 1 };
|
|
292
|
+
}
|
|
280
293
|
// A new launch consumes a measured additional slot. Compare consecutive
|
|
281
294
|
// measurements so repeated stale readings cannot grant that slot again.
|
|
282
295
|
this.budget = Math.max(0, this.budget + decision.recommended - (this.initialized ? this.lastFree : 0));
|
|
@@ -324,7 +337,7 @@ export class PodPoolScheduler {
|
|
|
324
337
|
const pendingJobs = this.lastRaw?.pendingJobs ?? [];
|
|
325
338
|
const lease = transferred ?? (free !== null && free > 0 ? this.hostLease.tryReserve((others) =>
|
|
326
339
|
others - this.hostLease.live().filter((r) => leaseHasPendingPod(r, pendingJobs) &&
|
|
327
|
-
(r.pid !== process.pid || r.startedAt !== this.hostLease.selfStart())).length < free) : null);
|
|
340
|
+
(r.pid !== process.pid || r.startedAt !== this.hostLease.selfStart())).length < free) : (gate && free === null ? this.hostLease.tryReserve(() => true) : null));
|
|
328
341
|
if (lease) {
|
|
329
342
|
let hostReleased = false;
|
|
330
343
|
const releaseHost = () => { if (!hostReleased) { hostReleased = true; lease(); } };
|
|
@@ -719,7 +732,7 @@ function defaultRemoteBuild(script: string, image: string): RemoteBuild {
|
|
|
719
732
|
* 📏 커밋이 바뀐 뒤 두 노드를 올리는 데 벽시계 55초(종전: 다시 굽기 1분 40초 ⊕ 4GB 차례 전송 5분 48초).
|
|
720
733
|
* 실패하면 = 종전의 통째 전송(syncPoolImage)으로 떨어진다.
|
|
721
734
|
*/
|
|
722
|
-
export async function syncPoolImages(members: readonly PodPoolMember[], image: string, localCommit: string | null, deps: { run?: RemoteRun; remoteBuild?: RemoteBuild; buildScript?: string | null; ship?: typeof syncPoolImage; localSkillsDigest?: string | null; waitIntervalMs?: number; waitMaxMs?: number; now?: () => number; sleep?: (ms: number) => Promise<void>; inspect?: LocalImageInspect; transfer?: ImageShip; pack?: PoolPack | null } = {}): Promise<Map<string, PoolImageSync>> {
|
|
735
|
+
export async function syncPoolImages(members: readonly PodPoolMember[], image: string, localCommit: string | null, deps: { run?: RemoteRun; remoteBuild?: RemoteBuild; buildScript?: string | null; ship?: typeof syncPoolImage; localSkillsDigest?: string | null; waitIntervalMs?: number; waitMaxMs?: number; now?: () => number; sleep?: (ms: number) => Promise<void>; inspect?: LocalImageInspect; transfer?: ImageShip; pack?: PoolPack | null; lock?: ImageShipLockOptions } = {}): Promise<Map<string, PoolImageSync>> {
|
|
723
736
|
const run = deps.run ?? defaultRemoteRun;
|
|
724
737
|
const script = deps.buildScript === undefined ? podImageBuildScript() : deps.buildScript;
|
|
725
738
|
const remoteBuild = deps.remoteBuild ?? (script ? defaultRemoteBuild(script, image) : null);
|
|
@@ -734,22 +747,61 @@ export async function syncPoolImages(members: readonly PodPoolMember[], image: s
|
|
|
734
747
|
const t0 = Date.now();
|
|
735
748
|
if (!m.sshHost) { results.set(m.context, { ok: true, action: 'local', detail: 'this machine', ms: 0 }); return; }
|
|
736
749
|
const before = remoteImageCommit(m, image, run);
|
|
737
|
-
// ⭐ «같은 판» = 커밋 ⊕ 스킬 해시 ⊕ (레지스트리 노드면) 레지스트리에 그 커밋 태그가 있다 — 스킬만 바뀌어도 다시 굽는다(대표 2026-09-26 «스킬 셋트 싱크»).
|
|
738
750
|
const skillsNow = deps.localSkillsDigest;
|
|
739
|
-
const skillsSame = skillsNow == null || remoteImageSkillsDigest(m, image, run) === skillsNow;
|
|
740
751
|
const refBefore = registryImageRef(m, localCommit, run, image);
|
|
741
752
|
const hasRegistry = run(m.sshHost, `docker inspect ${memberRegistry(m).split(':')[0]} >/dev/null 2>&1`).status === 0;
|
|
742
|
-
|
|
743
|
-
|
|
753
|
+
const registrySkillsSame = (ref: string) => skillsNow == null || remoteImageSkillsDigest(m, `localhost:${memberRegistry(m).split(':')[1]}/${ref.slice(ref.indexOf('/') + 1)}`, run) === skillsNow;
|
|
754
|
+
const skillsSame = hasRegistry && refBefore ? registrySkillsSame(refBefore) : skillsNow == null || remoteImageSkillsDigest(m, image, run) === skillsNow;
|
|
755
|
+
if (hasRegistry && refBefore && skillsSame) {
|
|
756
|
+
debug.log('pod.pool', 'image-fresh-registry-tag', { context: m.context, host: m.sshHost, commit: localCommit, localLabel: before, imageRef: refBefore, waitedMs: 0 });
|
|
757
|
+
results.set(m.context, { ok: true, action: 'fresh', detail: localCommit!.slice(0, 12), ms: Date.now() - t0, imageRef: refBefore });
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
if (!hasRegistry && localCommit && before === localCommit && skillsSame) {
|
|
761
|
+
results.set(m.context, { ok: true, action: 'fresh', detail: before.slice(0, 12), ms: Date.now() - t0 });
|
|
744
762
|
return;
|
|
745
763
|
}
|
|
746
764
|
if (remoteBuild) {
|
|
747
|
-
//
|
|
748
|
-
const
|
|
749
|
-
|
|
750
|
-
const
|
|
751
|
-
|
|
752
|
-
|
|
765
|
+
// Build and publish under a separate per-host lock: a failed build can then enter ship's synchronous lock.
|
|
766
|
+
const lockOpts = deps.lock ?? {};
|
|
767
|
+
const lockDir = lockOpts.dir ?? join(hostLeaseBaseDir(), 'image-ship');
|
|
768
|
+
const lockPath = `${imageShipLockPath(m.sshHost, lockDir)}.build`;
|
|
769
|
+
const retryMs = lockOpts.retryMs ?? 2_000;
|
|
770
|
+
const staleMs = lockOpts.staleMs ?? IMAGE_SHIP_TIMEOUT_MS + REMOTE_RUN_TIMEOUT_MS + 120_000;
|
|
771
|
+
const lockStarted = Date.now();
|
|
772
|
+
let b: { ok: boolean; detail: string };
|
|
773
|
+
try {
|
|
774
|
+
mkdirSync(lockDir, { recursive: true });
|
|
775
|
+
// Probe atomically rather than testing path existence: another process can acquire between that test and us.
|
|
776
|
+
let lock;
|
|
777
|
+
let waited = false;
|
|
778
|
+
try { lock = await acquireLockAsync(lockPath, { staleMs, retryBusyMs: 1, maxTries: 1 }); }
|
|
779
|
+
catch (error) {
|
|
780
|
+
if (!(error instanceof Error) || !error.message.startsWith('acquireLockAsync: timed out')) throw error;
|
|
781
|
+
waited = true;
|
|
782
|
+
debug.log('pod.pool', 'image-build-lock-wait', { context: m.context, host: m.sshHost, commit: localCommit, localLabel: before, imageRef: refBefore, waitedMs: Date.now() - lockStarted });
|
|
783
|
+
lock = await acquireLockAsync(lockPath, { staleMs, retryBusyMs: retryMs, maxTries: lockOpts.maxTries ?? Math.ceil(staleMs / retryMs) + 10 });
|
|
784
|
+
}
|
|
785
|
+
try {
|
|
786
|
+
if (waited && hasRegistry) {
|
|
787
|
+
const refNow = registryImageRef(m, localCommit, run, image);
|
|
788
|
+
if (refNow && registrySkillsSame(refNow)) {
|
|
789
|
+
debug.log('pod.pool', 'image-build-skipped-after-wait', { context: m.context, host: m.sshHost, commit: localCommit, localLabel: before, imageRef: refNow, waitedMs: Date.now() - lockStarted });
|
|
790
|
+
results.set(m.context, { ok: true, action: 'fresh', imageRef: refNow, detail: `${localCommit?.slice(0, 12)} (built by another process while waiting)`, ms: Date.now() - t0 });
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
// 같은 판이 아닌 멤버가 처음 여기 닿을 때 pack 한다(전부 fresh 면 pack 도 없다).
|
|
795
|
+
const shared = sharePack ? await (packed ??= sharePack()) : null;
|
|
796
|
+
if (shared && !shared.ok) debug.log('self-implement.pod', 'pool-image-pack-failed', { context: m.context, detail: shared.detail });
|
|
797
|
+
b = shared && !shared.ok
|
|
798
|
+
? { ok: false, detail: shared.detail }
|
|
799
|
+
: lock.stillHeld() ? await remoteBuild(m.sshHost, m.k3dCluster, m.registry, shared?.tgz) : { ok: false, detail: 'image build lock lost' };
|
|
800
|
+
if (!lock.stillHeld()) b = { ok: false, detail: 'image build lock lost' };
|
|
801
|
+
} finally { lock.release(); }
|
|
802
|
+
} catch (error) {
|
|
803
|
+
b = { ok: false, detail: `image build lock: ${error instanceof Error ? error.message : String(error)}` };
|
|
804
|
+
}
|
|
753
805
|
const after = remoteImageCommit(m, image, run);
|
|
754
806
|
const refAfter = registryImageRef(m, localCommit ?? after, run, image);
|
|
755
807
|
// ⭐ 레지스트리 노드면 «이 판 커밋 태그가 레지스트리에 있나»로 판정한다 — Pod 는 그 커밋 태그를 pull 한다.
|