elanous 0.2.23 → 0.2.24

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.
Files changed (347) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/chunks/{272-b23d2e92161d11fd.js → 272-0eed0fb0aab716b6.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/{3961-fbef064c213b2b18.js → 3961-aaae04ed1d32622f.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/app/approvals/{page-d5341ac96203a983.js → page-3b9fc39362473ae7.js} +1 -1
  6. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f2b62c19ca68fef8.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/app/board/{page-55c631c032dcc47e.js → page-d7602d489edf5f37.js} +1 -1
  8. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-ad3bd8084048bee4.js → page-24282bf9f90031a5.js} +1 -1
  9. package/apps/pwa/out/_next/static/chunks/app/bots/page-8b7c5ab6b5bc8d64.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/app/ceo/{page-5c5d0e370f495ef6.js → page-4eb84d454f8c3415.js} +1 -1
  11. package/apps/pwa/out/_next/static/chunks/app/chat/{page-c325e21543cd48a8.js → page-b2e494bdad57fcb5.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/app/consult/{page-6168a7c43e6ef8f5.js → page-c0ab229673a8b0eb.js} +1 -1
  13. package/apps/pwa/out/_next/static/chunks/app/control/page-3e2b45f3c36dbac4.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/app/decisions/page-c22aa511b28743ad.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/app/design-check/page-26345242e4049f55.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/app/editor/{page-25bb425afdb27541.js → page-bf88c0f558d8ef54.js} +1 -1
  17. package/apps/pwa/out/_next/static/chunks/app/exec/{page-32d72fdef1afe11a.js → page-52fec523e5b5d3ad.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/field/page-67723dfdcde92b31.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/app/inside/{page-2bd9ce9c020d02d4.js → page-22725e2570f1126b.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/intake/{page-a34096245e63b9f2.js → page-1891335950d68b70.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/app/layout-3267af1ebdc73cf5.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/app/live/{page-4cc66d22ca4ec502.js → page-c314b75c11234998.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/live-run/{page-83b14c729fcea837.js → page-9f9c85415f61782b.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/loops/page-c1d4276059085587.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/market/page-833aab587f26ad4c.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-ccdb83ed40c8d724.js → page-8526c17ae38e6a50.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/app/morning/{page-7ad3de8fa06fdea9.js → page-f998637a8402115c.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/not-found-b7bb53828b17b335.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/observatory/page-1b1850de0257e46d.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-786111f30b6f0969.js → page-6f23159fedb41f67.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-48fb31810693c357.js → page-1d809a87139dd024.js} +1 -1
  32. package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-4e17cce5e2ed0fee.js → page-da9e570234d481fd.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/app/outputs/{page-cb154acadb025414.js → page-b92caddb5c715f30.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/app/page-8ce3a38e210b810a.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-c2dd2b19c93f90ab.js → page-57ffeeb6019440fd.js} +1 -1
  36. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-abbb417bf514630b.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/app/sessions/{page-d3c007c8a0ddd7ee.js → page-2c2b3f29cec1efa4.js} +1 -1
  38. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-7837ce2aacb97b04.js → page-9369279f72fc7b51.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/app/settings/page-64861f14039499c3.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-ddc4c93efd8ab94b.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/app/setup/page-901a1391b81053bc.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/app/share/page-68d9bbaac9841a58.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/app/showroom/{page-119be9a0444d2482.js → page-b5c0c996f329371e.js} +1 -1
  44. package/apps/pwa/out/_next/static/chunks/app/tasks/{page-fa806c018833e332.js → page-89881ccbcf728978.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/app/term/page-0e87a803b3d7165e.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/today/{page-d431fbdea6ad93dd.js → page-58d4e5cb30ef4871.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/app/trace/page-02ce4c3b5f39e4d2.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/vault/page-e620d1bb8eadcec2.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-847c3de6ca8a4416.js → page-acf868c64d77f950.js} +1 -1
  50. package/apps/pwa/out/_next/static/chunks/app/workflows/page-54fa605fa6dba457.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-74e69ba2c8e970d4.js → page-bc79f0833533fa84.js} +1 -1
  52. package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-70038f41ae7cf931.js → page-d5c4398eea03d8f3.js} +1 -1
  53. package/apps/pwa/out/_next/static/css/72becfd42807b50d.css +1 -0
  54. package/apps/pwa/out/approvals/index.html +1 -1
  55. package/apps/pwa/out/approvals/index.txt +14 -14
  56. package/apps/pwa/out/autopilot/index.html +1 -1
  57. package/apps/pwa/out/autopilot/index.txt +14 -14
  58. package/apps/pwa/out/board/index.html +1 -1
  59. package/apps/pwa/out/board/index.txt +14 -14
  60. package/apps/pwa/out/botlab/index.html +1 -1
  61. package/apps/pwa/out/botlab/index.txt +14 -14
  62. package/apps/pwa/out/bots/index.html +1 -1
  63. package/apps/pwa/out/bots/index.txt +14 -14
  64. package/apps/pwa/out/ceo/index.html +1 -1
  65. package/apps/pwa/out/ceo/index.txt +14 -14
  66. package/apps/pwa/out/chat/index.html +1 -1
  67. package/apps/pwa/out/chat/index.txt +14 -14
  68. package/apps/pwa/out/consult/index.html +1 -1
  69. package/apps/pwa/out/consult/index.txt +14 -14
  70. package/apps/pwa/out/control/index.html +1 -1
  71. package/apps/pwa/out/control/index.txt +14 -14
  72. package/apps/pwa/out/decisions/index.html +1 -1
  73. package/apps/pwa/out/decisions/index.txt +14 -14
  74. package/apps/pwa/out/design-check/index.html +1 -1
  75. package/apps/pwa/out/design-check/index.txt +14 -14
  76. package/apps/pwa/out/editor/index.html +1 -1
  77. package/apps/pwa/out/editor/index.txt +14 -14
  78. package/apps/pwa/out/exec/index.html +1 -1
  79. package/apps/pwa/out/exec/index.txt +14 -14
  80. package/apps/pwa/out/field/index.html +1 -1
  81. package/apps/pwa/out/field/index.txt +14 -14
  82. package/apps/pwa/out/healthz +1 -1
  83. package/apps/pwa/out/index.html +1 -1
  84. package/apps/pwa/out/index.txt +14 -14
  85. package/apps/pwa/out/inside/index.html +1 -1
  86. package/apps/pwa/out/inside/index.txt +14 -14
  87. package/apps/pwa/out/intake/index.html +1 -1
  88. package/apps/pwa/out/intake/index.txt +14 -14
  89. package/apps/pwa/out/live/index.html +1 -1
  90. package/apps/pwa/out/live/index.txt +14 -14
  91. package/apps/pwa/out/live-run/index.html +1 -1
  92. package/apps/pwa/out/live-run/index.txt +14 -14
  93. package/apps/pwa/out/loops/index.html +1 -1
  94. package/apps/pwa/out/loops/index.txt +14 -14
  95. package/apps/pwa/out/market/index.html +1 -1
  96. package/apps/pwa/out/market/index.txt +14 -14
  97. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  98. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  99. package/apps/pwa/out/morning/index.html +1 -1
  100. package/apps/pwa/out/morning/index.txt +14 -14
  101. package/apps/pwa/out/observatory/index.html +1 -1
  102. package/apps/pwa/out/observatory/index.txt +14 -14
  103. package/apps/pwa/out/ops/checklist/index.html +1 -1
  104. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  105. package/apps/pwa/out/ops/release/index.html +1 -1
  106. package/apps/pwa/out/ops/release/index.txt +14 -14
  107. package/apps/pwa/out/ops/seats/index.html +1 -1
  108. package/apps/pwa/out/ops/seats/index.txt +14 -14
  109. package/apps/pwa/out/outputs/index.html +1 -1
  110. package/apps/pwa/out/outputs/index.txt +14 -14
  111. package/apps/pwa/out/reflection/index.html +1 -1
  112. package/apps/pwa/out/reflection/index.txt +15 -15
  113. package/apps/pwa/out/scheduler/index.html +1 -1
  114. package/apps/pwa/out/scheduler/index.txt +14 -14
  115. package/apps/pwa/out/sessions/index.html +1 -1
  116. package/apps/pwa/out/sessions/index.txt +14 -14
  117. package/apps/pwa/out/settings/devices/index.html +1 -1
  118. package/apps/pwa/out/settings/devices/index.txt +14 -14
  119. package/apps/pwa/out/settings/index.html +1 -1
  120. package/apps/pwa/out/settings/index.txt +14 -14
  121. package/apps/pwa/out/setup/done/index.html +1 -1
  122. package/apps/pwa/out/setup/done/index.txt +14 -14
  123. package/apps/pwa/out/setup/index.html +1 -1
  124. package/apps/pwa/out/setup/index.txt +14 -14
  125. package/apps/pwa/out/share/index.html +1 -1
  126. package/apps/pwa/out/share/index.txt +14 -14
  127. package/apps/pwa/out/showroom/index.html +1 -1
  128. package/apps/pwa/out/showroom/index.txt +14 -14
  129. package/apps/pwa/out/tasks/index.html +1 -1
  130. package/apps/pwa/out/tasks/index.txt +14 -14
  131. package/apps/pwa/out/term/index.html +1 -1
  132. package/apps/pwa/out/term/index.txt +14 -14
  133. package/apps/pwa/out/today/index.html +1 -1
  134. package/apps/pwa/out/today/index.txt +14 -14
  135. package/apps/pwa/out/trace/index.html +1 -1
  136. package/apps/pwa/out/trace/index.txt +14 -14
  137. package/apps/pwa/out/vault/index.html +1 -1
  138. package/apps/pwa/out/vault/index.txt +14 -14
  139. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  140. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  141. package/apps/pwa/out/workflows/index.html +1 -1
  142. package/apps/pwa/out/workflows/index.txt +14 -14
  143. package/apps/pwa/out/workspace/index.html +1 -1
  144. package/apps/pwa/out/workspace/index.txt +14 -14
  145. package/apps/pwa/out/worktrees/index.html +1 -1
  146. package/apps/pwa/out/worktrees/index.txt +14 -14
  147. package/catalog/models/openrouter/deepseek-v4-pro-0813.yaml +39 -0
  148. package/catalog/models/openrouter/deepseek-v4.1-flash.yaml +38 -0
  149. package/catalog/models/openrouter/glm-5.3-flash.yaml +35 -0
  150. package/catalog/providers/openrouter.yaml +2 -0
  151. package/graphs/implement-loop.yaml +31 -0
  152. package/graphs/ops/harness-gc.yaml +17 -0
  153. package/graphs/ops/recipes.yaml +3 -0
  154. package/graphs/overlays/README.md +1 -1
  155. package/graphs/overlays/stop-heal-ladder.yaml +105 -0
  156. package/package.json +1 -1
  157. package/scripts/add-api-key.sh +1 -1
  158. package/scripts/ci-typecheck-changed.ts +71 -14
  159. package/scripts/fleet/bedrock-pod-secret.ts +122 -0
  160. package/scripts/fleet/node-setup-linux.sh +97 -0
  161. package/scripts/fleet/node-setup.sh +11 -0
  162. package/scripts/fleet/zai-pod-secret.ts +86 -0
  163. package/scripts/land-global-checks.ts +3 -0
  164. package/scripts/measure-log-diet-2.ts +213 -0
  165. package/scripts/measure-tsc-affected-roots.ts +289 -0
  166. package/scripts/release-loop/gate-executor.ts +28 -0
  167. package/scripts/release-loop/gate-node.ts +262 -26
  168. package/scripts/release-loop/resume-release.ts +9 -3
  169. package/scripts/release-loop/resume.ts +41 -0
  170. package/scripts/release-loop/unattended-release.ts +22 -7
  171. package/scripts/tsc-baseline-cache.ts +147 -0
  172. package/scripts/usage-rollup.ts +58 -6
  173. package/src/agent/run-context.ts +10 -0
  174. package/src/agent-mission/driver.ts +18 -3
  175. package/src/agent-mission/resource-ladder.ts +8 -3
  176. package/src/agent-mission/review-loop.ts +8 -2
  177. package/src/agent-substrate/acp-implementer.ts +139 -0
  178. package/src/agent-substrate/pr-reviewer.ts +31 -4
  179. package/src/agent-substrate/self-review-cli.ts +48 -9
  180. package/src/autopilot/mission-critique.ts +18 -5
  181. package/src/autopilot/mission-feasibility-negotiate.ts +11 -10
  182. package/src/autopilot/mission-trace.ts +12 -1
  183. package/src/autopilot/system-lookback-run.ts +16 -10
  184. package/src/bench/bench.ts +183 -0
  185. package/src/budget/fetchers/grok.ts +15 -5
  186. package/src/budget/llm-cost.ts +37 -5
  187. package/src/budget/types.ts +8 -2
  188. package/src/budget/unified-usage.ts +88 -6
  189. package/src/cli/bench-cli.ts +34 -0
  190. package/src/cli/guardian-cli.ts +26 -1
  191. package/src/cli/image-cli.ts +101 -0
  192. package/src/cli/log-event-names.ts +4 -0
  193. package/src/cli/pr-cli.ts +62 -5
  194. package/src/cli/pr-land-overlap-evidence.ts +160 -12
  195. package/src/cli/release-cli.ts +18 -9
  196. package/src/cli/rfc-status.ts +52 -2
  197. package/src/cli/self-update.ts +104 -0
  198. package/src/cli/tasks-cli.ts +109 -4
  199. package/src/cli/usage-cli.ts +91 -8
  200. package/src/core-turn/run-goal-loop.ts +5 -4
  201. package/src/dashboard/ad-run-setup-bindings.ts +279 -0
  202. package/src/dashboard/index.ts +18 -276
  203. package/src/dashboard/stale-install-check.ts +11 -0
  204. package/src/debug/log-diet.ts +38 -7
  205. package/src/debug/log.ts +19 -1
  206. package/src/domains/release-tool.ts +39 -0
  207. package/src/error-report/report.ts +21 -4
  208. package/src/github/pr-labels.ts +1 -0
  209. package/src/graph-runner/graph-cli.ts +42 -1
  210. package/src/graph-runner/runner.ts +167 -6
  211. package/src/grok/usage.ts +23 -2
  212. package/src/grounding/ref-index.ts +253 -0
  213. package/src/harness/dispatch-backpressure.ts +10 -2
  214. package/src/harness/harness-clean.ts +199 -78
  215. package/src/harness/harness-cli-command.ts +122 -7
  216. package/src/harness/harness-gc.ts +331 -0
  217. package/src/harness/harness-stop.ts +4 -2
  218. package/src/harness/harness-substrate-default.ts +142 -7
  219. package/src/harness/launch-registry.ts +6 -0
  220. package/src/harness/worktree-branch-prefix.ts +9 -0
  221. package/src/hq/fence.ts +187 -0
  222. package/src/hq/seats.ts +38 -22
  223. package/src/images/image-ledger.ts +179 -0
  224. package/src/index.ts +93 -11
  225. package/src/instance/child-scope.ts +32 -1
  226. package/src/instance/dev-project.ts +133 -0
  227. package/src/instance/resolve.ts +24 -0
  228. package/src/instance/spawn-elanous.ts +45 -0
  229. package/src/intake-plane/ref-prefetch.ts +110 -0
  230. package/src/intelligence-map/model-catalog.ts +72 -0
  231. package/src/llm/bedrock-eventstream.ts +215 -0
  232. package/src/llm/bedrock-provider.ts +114 -41
  233. package/src/llm/bedrock.ts +174 -28
  234. package/src/llm/openrouter-byok-cost.ts +96 -0
  235. package/src/llm/openrouter-deepseek.ts +151 -0
  236. package/src/llm/openrouter-upstream-pins.config.ts +25 -0
  237. package/src/llm/zai-coding-plan.ts +700 -0
  238. package/src/llm.ts +87 -16
  239. package/src/loops/checker.ts +148 -9
  240. package/src/loops/orchestrator/resync-shadow.ts +34 -7
  241. package/src/maturity/feature-maturity.ts +2 -2
  242. package/src/model-tier/llm-tier-map.ts +9 -5
  243. package/src/models/prompts.ts +2 -1
  244. package/src/mss/logging/log-store.ts +58 -11
  245. package/src/nexus/api/missions.ts +73 -2
  246. package/src/nexus/api/ops-api.ts +18 -0
  247. package/src/nexus/index.ts +7 -75
  248. package/src/nexus/tools-kind.ts +78 -0
  249. package/src/oauth/codex-account-rotation.ts +39 -2
  250. package/src/oauth/codex-account-store.ts +323 -24
  251. package/src/oauth/fallback-chain.ts +53 -15
  252. package/src/onboarding/first-run.ts +37 -18
  253. package/src/onboarding/web-first.ts +20 -2
  254. package/src/ops/machine-heartbeat.ts +185 -0
  255. package/src/ops/overview-cache.ts +175 -0
  256. package/src/ops/overview.ts +8 -0
  257. package/src/prompt-cache/openai.ts +3 -0
  258. package/src/prompt-cache/types.ts +5 -0
  259. package/src/prompt-enhance/enhance.ts +23 -0
  260. package/src/release-loop/placement.ts +16 -1
  261. package/src/requirements/requirement-ledger.ts +536 -0
  262. package/src/retro/daily.ts +5 -5
  263. package/src/seat-loop/seat-loop.ts +32 -10
  264. package/src/self-dev/dev-cli.ts +44 -10
  265. package/src/self-dev/dev-pipeline.ts +7 -2
  266. package/src/self-dev/draft-residue.ts +14 -2
  267. package/src/self-dev/draft-sweep.ts +103 -0
  268. package/src/self-dev/graph-journey-route.ts +6 -0
  269. package/src/self-dev/orchestrate-cli.ts +25 -3
  270. package/src/self-dev/orchestrate.ts +41 -6
  271. package/src/self-dev/run-supervisor.ts +188 -15
  272. package/src/self-dev/run-types.ts +13 -0
  273. package/src/self-dev/supervisor-verdict-edges.ts +1 -0
  274. package/src/self-implement/budget-gate.ts +53 -13
  275. package/src/self-implement/child-context-composition.ts +139 -0
  276. package/src/self-implement/child-llm-preference.ts +13 -5
  277. package/src/self-implement/claude-acp-implement.ts +67 -0
  278. package/src/self-implement/flex-signals.ts +59 -0
  279. package/src/self-implement/frozen-merges.ts +8 -3
  280. package/src/self-implement/goal-author-decompose.ts +10 -2
  281. package/src/self-implement/goal-author.ts +24 -9
  282. package/src/self-implement/graph-overlay-condition.ts +28 -0
  283. package/src/self-implement/graph-overlay-yaml.ts +41 -7
  284. package/src/self-implement/graph-templates.ts +33 -1
  285. package/src/self-implement/graph-yaml.ts +56 -0
  286. package/src/self-implement/heal-intake.ts +5 -1
  287. package/src/self-implement/orchestrator.ts +144 -15
  288. package/src/self-implement/running-runs.ts +75 -5
  289. package/src/self-implement/seams.ts +47 -1
  290. package/src/self-implement/self-implement-cli.ts +9 -4
  291. package/src/self-implement/stop-autoheal.ts +69 -2
  292. package/src/session-runtime/retry-policy.ts +2 -1
  293. package/src/steward/launch.ts +5 -4
  294. package/src/steward/triage-budget.ts +62 -0
  295. package/src/steward/triage.ts +83 -22
  296. package/src/task-agent/actions.ts +62 -5
  297. package/src/task-agent/completion-evidence.ts +5 -0
  298. package/src/task-agent/cover.ts +101 -3
  299. package/src/task-agent/judge.ts +16 -0
  300. package/src/task-agent/land-order-ledger.ts +95 -0
  301. package/src/task-agent/live-move-history.ts +87 -1
  302. package/src/task-agent/live-moves.ts +350 -20
  303. package/src/task-agent/rejudge-on-head.ts +75 -20
  304. package/src/task-agent/review-prior-fix.ts +146 -0
  305. package/src/task-agent/run-adopt.ts +136 -0
  306. package/src/task-agent/shadow.ts +53 -9
  307. package/src/task-agent/task-hand.ts +54 -7
  308. package/src/task-orchestrator/surfaces/pod-account-broker.ts +38 -5
  309. package/src/task-orchestrator/surfaces/pod-bedrock-child.ts +74 -0
  310. package/src/task-orchestrator/surfaces/pod-child-default.ts +50 -0
  311. package/src/task-orchestrator/surfaces/pod-child-providers.ts +14 -1
  312. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +79 -6
  313. package/src/task-orchestrator/surfaces/pod-lease.ts +17 -5
  314. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +11 -5
  315. package/src/task-orchestrator/surfaces/pod-pool.ts +20 -4
  316. package/src/task-orchestrator/surfaces/pod-zai-child.ts +101 -0
  317. package/src/task-orchestrator/surfaces/self-implement-pod.ts +213 -23
  318. package/src/task-orchestrator/surfaces/self-implement.ts +19 -1
  319. package/src/typecheck-ratchet.ts +1 -0
  320. package/src/user-config.ts +51 -11
  321. package/src/version/packed-revision.json +1 -1
  322. package/src/workflow-runtime/triggers/registry.ts +59 -0
  323. package/src/workflow-runtime/triggers/webhook-source.ts +35 -5
  324. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-0160ab83c8685a12.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/app/bots/page-e6784759a2531031.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/app/control/page-31417d3604154a6d.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/decisions/page-e9606dc0bdec9305.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/design-check/page-a648bc464c9dc754.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/field/page-d27b794c8ba76bbb.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/layout-22170f4f00a15496.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/loops/page-6e2781c523dad761.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/market/page-26dd415c8f13f341.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/not-found-015c02c4fec4023e.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/observatory/page-6b0ae5b5c42f6d2f.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/page-16258e956ea93fc2.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-85f35dd1dd96ae87.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/app/settings/page-7350ee93f4fe1033.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-00723458575f2b19.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/app/setup/page-745d9b6c015076f8.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/app/share/page-736d33c7414a8a1a.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/app/term/page-c782041b5f5199ba.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/app/trace/page-0c056abff82b9891.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/app/vault/page-242d668e4fbf72c4.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/app/workflows/page-81dd0d8ae923ed72.js +0 -1
  345. package/apps/pwa/out/_next/static/css/5619d59e87eddf66.css +0 -1
  346. /package/apps/pwa/out/_next/static/{rfkMm4movqkqMebBrCXiR → Q-7iofoxOEv1WX18S35of}/_buildManifest.js +0 -0
  347. /package/apps/pwa/out/_next/static/{rfkMm4movqkqMebBrCXiR → Q-7iofoxOEv1WX18S35of}/_ssgManifest.js +0 -0
@@ -0,0 +1,97 @@
1
+ # shellcheck shell=bash
2
+ # 리눅스 노드 갈래 — node-setup.sh 가 원격 `uname -s` = Linux 일 때 source 한다(WORKER-WSL-LINUX-POOL · OP 2026-10-11 host-e-wsl 실측).
3
+ # 맥 갈래(brew·OrbStack·Chrome·~/.zshenv) 대신: apt 기본 패키지 · systemd · docker-ce(공식 apt) · docker 서비스·그룹 · ~/.docker-fleet(빈 currentContext)
4
+ # · k3d(공식 install.sh) · kubectl(k3s 와 같은 판 · sha256 검증) · nodejs(codex CLI 셔뱅) · codex CLI(이 맥과 같은 판) · 미러(HEAD=main).
5
+ # bun · 레지스트리 · 클러스터 · kubeconfig · 정책 · DNS · 판 대조는 맥과 «같은» 단계를 탄다(node-setup.sh).
6
+ # ⭐ 단계마다 «이미 돼 있으면 건너뛴다»(멱등) · --check 면 바꾸지 않고 → 할 일만.
7
+ # ⛔ sudo 는 «sudo -n» 으로만 — 비대화형 ssh 에서 비번을 묻고 멈추지 않는다(묻으면 ✗ ⊕ 사람이 칠 명령).
8
+ # ⛔ 코드 밖(윈도우 Startup .cmd · .wslconfig · 자격 · codexHome)은 실행하지 않는다 — linux_handoff 가 «사람 손 할 일»로 출력만.
9
+ # 아키텍처가 이 맥과 다르다(x86_64) → Pod 이미지는 그 노드에서 `ELANOUS_BUILD_REMOTE=<호스트>` 로 굽는다(맥 arm64 이미지 못 씀).
10
+
11
+ KUBECTL_VER="${ELANOUS_FLEET_KUBECTL_VERSION:-${K3S_IMAGE##*:}}"; KUBECTL_VER="${KUBECTL_VER%%-k3s*}" # 풀의 k3s 와 같은 판
12
+ CODEX_VER="${ELANOUS_FLEET_CODEX_VERSION:-$(codex --version 2>/dev/null | awk '{print $NF}')}" # 이 맥과 같은 판
13
+ APT_BASE="ca-certificates curl unzip git python3" # unzip = bun 설치기
14
+
15
+ linux_base() {
16
+ ok "OS Linux $(R 'uname -m' 2>/dev/null)$(R '. /etc/os-release 2>/dev/null && printf " · %s" "$PRETTY_NAME"' 2>/dev/null)"
17
+ # L1. apt 기본 패키지
18
+ if R 'for c in curl unzip git python3; do command -v $c >/dev/null || exit 1; done'; then ok "기본 패키지(curl·unzip·git·python3)"
19
+ elif [ $CHECK = 1 ]; then todo "apt 기본 패키지($APT_BASE)"
20
+ else R "{ sudo -n apt-get update -qq && sudo -n apt-get install -y -qq $APT_BASE; } >/tmp/fleet-apt.log 2>&1" && ok "기본 패키지 (설치)" || bad "기본 패키지 설치 실패 — 원격 /tmp/fleet-apt.log (sudo 비번이면 사람이: sudo apt-get install -y $APT_BASE)"; fi
21
+ # L2. systemd — docker·containerd 를 서비스로 띄우려면 PID 1 이 systemd 여야 한다(WSL 은 /etc/wsl.conf 로 켠다).
22
+ if R 'test "$(ps -p 1 -o comm= 2>/dev/null)" = systemd'; then ok "systemd (PID 1)"
23
+ else bad "systemd 가 PID 1 이 아니다 — WSL 이면 /etc/wsl.conf 에 [boot] systemd=true 뒤 wsl --shutdown(사람이)"; fi
24
+ # L3. docker — Docker Desktop shim 이면 멈춘다: 엔진이 윈도우 쪽 Desktop VM 이라 k3d 풀이 그 VM 에 뜬다(배포판 안 docker-ce 가 아니다).
25
+ SHIM="$(R 'p=$(command -v docker 2>/dev/null) && case "$(readlink -f "$p")" in /mnt/wsl/*|*docker-desktop*) echo shim;; esac; docker info --format "{{.OperatingSystem}}" 2>/dev/null | grep -qi "docker desktop" && echo shim' 2>/dev/null)"
26
+ if [ -n "$SHIM" ]; then bad "docker 가 Docker Desktop shim 이다 — Desktop 설정에서 이 배포판의 WSL integration 을 끄고(사람이) 다시 돌리면 docker-ce 를 깐다"; echo; echo "✗ $HOST 미완 — 위 ✗ 를 먼저"; exit 1; fi
27
+ LX_DOCKER_PENDING=0
28
+ if R 'command -v dockerd >/dev/null && dpkg -s docker-ce >/dev/null 2>&1'; then ok "docker-ce (apt)"
29
+ elif [ $CHECK = 1 ]; then todo "docker-ce 설치(공식 apt 저장소 · docker-ce·cli·containerd.io·buildx)"; LX_DOCKER_PENDING=1
30
+ else R '{ sudo -n install -m 0755 -d /etc/apt/keyrings && . /etc/os-release && sudo -n curl -fsSL "https://download.docker.com/linux/$ID/gpg" -o /etc/apt/keyrings/docker.asc && sudo -n chmod a+r /etc/apt/keyrings/docker.asc && printf "Types: deb\nURIs: https://download.docker.com/linux/%s\nSuites: %s\nComponents: stable\nArchitectures: %s\nSigned-By: /etc/apt/keyrings/docker.asc\n" "$ID" "$VERSION_CODENAME" "$(dpkg --print-architecture)" | sudo -n tee /etc/apt/sources.list.d/docker.sources >/dev/null && sudo -n apt-get update -qq && sudo -n apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-buildx-plugin; } >/tmp/fleet-docker-apt.log 2>&1' && ok "docker-ce (설치 · 공식 apt)" || { bad "docker-ce 설치 실패 — 원격 /tmp/fleet-docker-apt.log (sudo 비번이면 사람이)"; LX_DOCKER_PENDING=1; }; fi
31
+ if R 'systemctl is-enabled docker containerd >/dev/null 2>&1 && systemctl is-active docker containerd >/dev/null 2>&1'; then ok "docker·containerd 서비스(enable · active)"
32
+ elif [ $CHECK = 1 ]; then todo "systemctl enable --now docker containerd"; LX_DOCKER_PENDING=1
33
+ else R 'sudo -n systemctl enable --now docker containerd >/tmp/fleet-docker-svc.log 2>&1' && ok "docker·containerd 서비스 (enable --now)" || { bad "docker 서비스 기동 실패 — 원격 /tmp/fleet-docker-svc.log"; LX_DOCKER_PENDING=1; }; fi
34
+ # 그룹은 «그룹 db»로 잰다(id -nG <사용자>) — 지금 세션 그룹이 아니라 다음 ssh 연결이 받을 그룹.
35
+ if R 'id -nG "$(id -un)" | tr " " "\n" | grep -qx docker'; then ok "docker 그룹"
36
+ elif [ $CHECK = 1 ]; then todo "usermod -aG docker <사용자>"; LX_DOCKER_PENDING=1
37
+ else R 'sudo -n usermod -aG docker "$(id -un)"' && ok "docker 그룹 (추가 · 새 ssh 연결부터)" || { bad "docker 그룹 추가 실패"; LX_DOCKER_PENDING=1; }; fi
38
+ if R 'docker info --format "{{.ServerVersion}}"' >/dev/null 2>&1; then ok "docker $(R 'docker info --format "{{.ServerVersion}} cpu={{.NCPU}} mem={{.MemTotal}}"')"
39
+ elif [ $LX_DOCKER_PENDING = 0 ]; then bad "docker 엔진에 못 붙는다(설치·서비스·그룹은 됐다)"; fi
40
+ # L4. 플릿 docker 설정 — 리눅스엔 키체인이 없어도 같은 DOCKER_CONFIG 를 쓴다(build.sh ELANOUS_BUILD_REMOTE). 빈 currentContext = 기본 소켓 · buildx 는 시스템 플러그인.
41
+ if R 'test -f ~/.docker-fleet/config.json && DOCKER_CONFIG=~/.docker-fleet docker info >/dev/null 2>&1 && DOCKER_CONFIG=~/.docker-fleet docker buildx version >/dev/null 2>&1'; then ok "플릿 docker 설정(~/.docker-fleet · buildx)"
42
+ elif [ $CHECK = 1 ]; then todo "~/.docker-fleet(빈 currentContext)"
43
+ else R 'mkdir -p ~/.docker-fleet && { test -f ~/.docker-fleet/config.json || printf "{\"currentContext\": \"\"}\n" > ~/.docker-fleet/config.json; }'
44
+ R 'DOCKER_CONFIG=~/.docker-fleet docker info >/dev/null 2>&1 && DOCKER_CONFIG=~/.docker-fleet docker buildx version >/dev/null 2>&1' && ok "플릿 docker 설정 (~/.docker-fleet 생성 · buildx)" || bad "플릿 docker 설정으로 docker 에 못 붙는다"; fi
45
+ }
46
+
47
+ linux_tools() {
48
+ # L5. k3d — 공식 설치 스크립트(/usr/local/bin)
49
+ if R 'command -v k3d' >/dev/null; then ok "k3d $(R 'k3d version | head -1 | cut -d" " -f3')"
50
+ elif [ $CHECK = 1 ]; then todo "k3d 설치(공식 install.sh)"
51
+ else R 'curl -fsSL https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | sudo -n bash >/tmp/fleet-k3d.log 2>&1' && R 'command -v k3d' >/dev/null && ok "k3d (설치)" || bad "k3d 설치 실패 — 원격 /tmp/fleet-k3d.log"; fi
52
+ # L6. kubectl — 클러스터와 같은 판 · sha256 이 맞을 때만 설치한다(원격 클러스터 점검이 이것을 쓴다).
53
+ kv="$(R 'kubectl version --client -o json 2>/dev/null | python3 -c "import json,sys;print(json.load(sys.stdin)[\"clientVersion\"][\"gitVersion\"])"' 2>/dev/null)"
54
+ if [ "$kv" = "$KUBECTL_VER" ]; then ok "kubectl $kv"
55
+ elif [ $CHECK = 1 ]; then todo "kubectl $KUBECTL_VER 설치(지금 ${kv:-없음} · sha256 검증)"
56
+ else R "{ a=\$(dpkg --print-architecture) && mkdir -p /tmp/fleet-kubectl-$KUBECTL_VER && cd /tmp/fleet-kubectl-$KUBECTL_VER && curl -fsSLo kubectl https://dl.k8s.io/release/$KUBECTL_VER/bin/linux/\$a/kubectl && curl -fsSLo kubectl.sha256 https://dl.k8s.io/release/$KUBECTL_VER/bin/linux/\$a/kubectl.sha256 && echo \"\$(cat kubectl.sha256) kubectl\" | sha256sum --check && sudo -n install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl; } >/tmp/fleet-kubectl.log 2>&1"
57
+ kv="$(R 'kubectl version --client -o json 2>/dev/null | python3 -c "import json,sys;print(json.load(sys.stdin)[\"clientVersion\"][\"gitVersion\"])"' 2>/dev/null)"
58
+ [ "$kv" = "$KUBECTL_VER" ] && ok "kubectl $kv (설치 · sha256 검증)" || bad "kubectl $KUBECTL_VER 설치 실패(sha256 이 틀리면 설치 안 함) — 원격 /tmp/fleet-kubectl.log"; fi
59
+ # L7. nodejs — codex CLI 가 node 셔뱅이다(없으면 usage 가 «app-server exited» 로 죽는다).
60
+ if R 'command -v node' >/dev/null; then ok "node $(R 'node --version')"
61
+ elif [ $CHECK = 1 ]; then todo "nodejs 설치(apt · codex CLI 셔뱅)"
62
+ else R 'sudo -n apt-get install -y -qq nodejs >/tmp/fleet-node.log 2>&1' && R 'command -v node' >/dev/null && ok "node (설치 · apt)" || bad "nodejs 설치 실패 — 원격 /tmp/fleet-node.log"; fi
63
+ # L8. codex CLI — 이 맥과 같은 판(bun 전역)
64
+ cv="$(R 'codex --version 2>/dev/null' 2>/dev/null | awk '{print $NF}')"
65
+ if [ -n "$cv" ] && { [ -z "$CODEX_VER" ] || [ "$cv" = "$CODEX_VER" ]; }; then ok "codex $cv"
66
+ elif [ $CHECK = 1 ]; then todo "codex CLI ${CODEX_VER:-?} (지금 ${cv:-없음} · bun add -g @openai/codex@<이 맥 판>)"
67
+ elif [ -z "$CODEX_VER" ]; then bad "이 맥에 codex 가 없어 판을 모른다 — ELANOUS_FLEET_CODEX_VERSION 으로 준다"
68
+ else R "bun add -g @openai/codex@$CODEX_VER >/tmp/fleet-codex.log 2>&1"; cv="$(R 'codex --version 2>/dev/null' 2>/dev/null | awk '{print $NF}')"
69
+ [ "$cv" = "$CODEX_VER" ] && ok "codex $cv (설치)" || bad "codex $CODEX_VER 설치 실패 — 원격 /tmp/fleet-codex.log"; fi
70
+ }
71
+
72
+ # tailnet 이름 — 인자(ELANOUS_FLEET_TSNAME)가 먼저 · 없으면 원격 tailscale(맥 갈래와 같은 명령) · WSL 이면 윈도우 쪽 tailscale.exe.
73
+ linux_tsname() {
74
+ if [ -n "${ELANOUS_FLEET_TSNAME:-}" ]; then TSNAME="$ELANOUS_FLEET_TSNAME"; return; fi
75
+ [ -n "$TSNAME" ] || TSNAME="$(R 'tailscale.exe status --self --json 2>/dev/null | python3 -c "import json,sys;print(json.load(sys.stdin)[\"Self\"][\"DNSName\"].rstrip(\".\"))"' 2>/dev/null)"
76
+ }
77
+
78
+ # 미러 — HEAD 는 main(기본 master 면 첫 clone 이 깨진다). 이미 있고 HEAD 가 master 면 symbolic-ref 만 고친다(객체는 안 건드린다).
79
+ linux_mirror() {
80
+ if R "test -d ~/mirror/elanous-agent.git"; then
81
+ if [ "$(R 'git --git-dir="$HOME/mirror/elanous-agent.git" symbolic-ref HEAD 2>/dev/null')" = refs/heads/main ]; then ok "호스트 git 미러 ~/mirror/elanous-agent.git (HEAD=main)"
82
+ elif [ $CHECK = 1 ]; then todo "호스트 git 미러 HEAD 를 main 으로(git symbolic-ref HEAD refs/heads/main)"
83
+ else R 'git --git-dir="$HOME/mirror/elanous-agent.git" symbolic-ref HEAD refs/heads/main' && ok "호스트 git 미러 (HEAD=main 으로 고침)" || bad "미러 HEAD 고치기 실패"; fi
84
+ elif [ $CHECK = 1 ]; then todo "호스트 git 미러 ~/mirror/elanous-agent.git 생성(git init --bare -b main) ⊕ 이 맥에서 첫 push"
85
+ else R "mkdir -p ~/mirror && git init -q --bare -b main ~/mirror/elanous-agent.git" && ok "호스트 git 미러 (생성 · HEAD=main · 첫 push 는 이 맥 크론)" || { bad "미러 생성 실패"; exit 1; }; fi
86
+ }
87
+
88
+ # 코드 밖 — 실행하지 않는다. 자격은 «어디에 무엇»만 적고 값을 읽거나 옮기지 않는다.
89
+ linux_handoff() {
90
+ echo
91
+ echo "✋ 사람 손 할 일 (코드 밖 · 이 스크립트는 실행하지 않는다)"
92
+ echo " 1. 윈도우 Startup 폴더에 .cmd — wsl.exe -d <배포판 이름> (⚠️ 기본 배포판이 docker-desktop 일 수 있다 — -d 필수)"
93
+ echo " 2. %UserProfile%\\.wslconfig — [wsl2] memory=40GB · vmIdleTimeout=-1 (뒤 wsl --shutdown)"
94
+ echo " 3. 자격(대표 승인 뒤 · 값은 사람이 옮긴다): Pod Secret 둘 · ~/.cache/{openrouter,zai}_api_key · ~/.grok/auth.json · codex = ~/.elanous/auth.json 의 openai-codex* ⊕ ~/.codex*/auth.json"
95
+ echo " 4. codexHome 경로를 리눅스 홈(/home/<사용자>/…)으로 — 맥 경로 그대로면 usage 가 «authentication required»"
96
+ echo " 5. Pod 이미지는 이 노드에서 굽는다 — ELANOUS_BUILD_REMOTE=$HOST (x86_64 · 맥 arm64 이미지 못 씀)"
97
+ }
@@ -3,6 +3,7 @@
3
3
  # bash scripts/fleet/node-setup.sh <호스트> [--check]
4
4
  # <호스트> ssh 로 닿는 이름(예: node-b · node-c) — 이 맥에서 `ssh -o BatchMode=yes <호스트> true` 가 돼야 한다.
5
5
  # --check 영구 설정을 바꾸지 않고 상태만 본다(단계마다 ✓ · ✗ · → 할 일). DNS 점검용 Pod 는 잠깐 만들었다 지운다.
6
+ # 원격 `uname -s` = Linux(WSL Ubuntu 등)면 brew·OrbStack·Chrome·~/.zshenv 대신 리눅스 갈래(scripts/fleet/node-setup-linux.sh)를 탄다.
6
7
  # ⭐ 단계마다 «이미 돼 있으면 건너뛴다» — 몇 번 돌려도 같은 결과(재현 가능한 셋업).
7
8
  # ⛔ 원격의 다른 컨테이너·앱은 건드리지 않는다. 이 맥의 ~/.kube/config 는 바꾸기 전에 백업한다.
8
9
  set -u
@@ -20,6 +21,9 @@ echo "▶ 노드 $HOST$([ $CHECK = 1 ] && echo ' (점검만)')"
20
21
 
21
22
  # 1. ssh
22
23
  R true >/dev/null 2>&1 && ok "ssh" || { bad "ssh 불통 — 이 맥에서 ssh 키·Tailscale 확인"; exit 1; }
24
+ # 1b. 원격 OS — Linux(WSL Ubuntu 등)면 리눅스 갈래(scripts/fleet/node-setup-linux.sh · apt·docker-ce·k3d install.sh). 맥은 아래 종전 단계 그대로.
25
+ NODE_OS="$(R 'uname -s' 2>/dev/null)"
26
+ if [ "$NODE_OS" = Linux ]; then . "$ROOT/scripts/fleet/node-setup-linux.sh"; linux_base; else
23
27
  # 2. brew · docker(OrbStack)
24
28
  R 'command -v brew' >/dev/null && ok "brew" || bad "brew 없음 — https://brew.sh 로 먼저 설치(사람이)"
25
29
  if R 'docker info --format "{{.ServerVersion}}"' >/dev/null 2>&1; then ok "docker $(R 'docker info --format "{{.ServerVersion}} cpu={{.NCPU}} mem={{.MemTotal}}"')"; else bad "docker 없음·꺼짐 — OrbStack 을 설치·실행(사람이 · ⛔ OrbStack 내장 k8s 는 NetworkPolicy 를 집행 안 한다 · k3d 를 쓴다)"; fi
@@ -36,6 +40,7 @@ if R 'test -f ~/.docker-fleet/config.json && DOCKER_CONFIG=~/.docker-fleet docke
36
40
  elif [ $CHECK = 1 ]; then todo "~/.docker-fleet(키체인 없는 docker 설정)"
37
41
  else R 'mkdir -p ~/.docker-fleet && ctx=$(python3 -c "import json,os;print(json.load(open(os.path.expanduser(\"~/.docker/config.json\"))).get(\"currentContext\",\"\"))" 2>/dev/null); printf "{\"currentContext\": \"%s\"}\n" "$ctx" > ~/.docker-fleet/config.json; [ -e ~/.docker-fleet/contexts ] || ln -s ~/.docker/contexts ~/.docker-fleet/contexts; [ -e ~/.docker-fleet/cli-plugins ] || [ ! -d ~/.docker/cli-plugins ] || ln -s ~/.docker/cli-plugins ~/.docker-fleet/cli-plugins'
38
42
  R 'DOCKER_CONFIG=~/.docker-fleet docker info >/dev/null 2>&1 && DOCKER_CONFIG=~/.docker-fleet docker buildx version >/dev/null 2>&1' && ok "플릿 docker 설정 (~/.docker-fleet 생성 · buildx)" || bad "플릿 docker 설정으로 docker 에 못 붙는다"; fi
43
+ fi
39
44
  # 3. bun — 이 맥과 «같은 판»(시험 샤딩은 판이 같아야 비교된다).
40
45
  # ⛔ 원격에 «다른 판» bun 이 이미 있으면 덮어쓰지 않는다(그 기계의 다른 일이 쓴다) — ~/.bun-<판> 에 나란히 둔다.
41
46
  # ⛔ 나란히 설치할 땐 설치기가 셸 설정에 PATH 를 덧붙여 기본 bun 을 가로채지 않게, 셸 설정 파일을 전후로 되돌린다.
@@ -46,6 +51,7 @@ elif [ "$side" = "$BUN_VER" ]; then ok "bun $side (~/.bun-$BUN_VER · 기본 bun
46
51
  elif [ $CHECK = 1 ]; then todo "bun $BUN_VER 설치(기본 bun ${have:-없음} — $([ -n "$have" ] && echo '건드리지 않고 ~/.bun-'"$BUN_VER"' 에 나란히' || echo '~/.bun'))"
47
52
  elif [ -z "$have" ]; then R "curl -fsSL https://bun.sh/install | bash -s bun-v$BUN_VER >/dev/null 2>&1"; [ "$(R 'bun --version')" = "$BUN_VER" ] && ok "bun $BUN_VER (설치 · ~/.bun)" || bad "bun 설치 실패"
48
53
  else R "for f in .zshrc .bashrc .bash_profile .zprofile .profile .config/fish/config.fish; do [ -f \$HOME/\$f ] && cp -p \$HOME/\$f \$HOME/\$f.fleet-bak; done; curl -fsSL https://bun.sh/install | BUN_INSTALL=\$HOME/.bun-$BUN_VER bash -s bun-v$BUN_VER >/dev/null 2>&1; for f in .zshrc .bashrc .bash_profile .zprofile .profile .config/fish/config.fish; do [ -f \$HOME/\$f.fleet-bak ] && mv \$HOME/\$f.fleet-bak \$HOME/\$f; done"; [ "$(R "$FLEET_BUN --version")" = "$BUN_VER" ] && ok "bun $BUN_VER (설치 · ~/.bun-$BUN_VER · 기본 bun $have 그대로)" || bad "bun 나란히 설치 실패"; fi
54
+ if [ "$NODE_OS" = Linux ]; then linux_tools; else
49
55
  # 4. Chrome (CDP 시험 레인 · 이 맥과 같은 조건)
50
56
  if R 'test -d "/Applications/Google Chrome.app"'; then ok "Chrome"
51
57
  elif [ $CHECK = 1 ]; then todo "Chrome 설치"
@@ -54,8 +60,10 @@ else R 'brew install --cask google-chrome >/tmp/fleet-chrome.log 2>&1' && ok "Ch
54
60
  if R 'command -v k3d' >/dev/null; then ok "k3d $(R 'k3d version | head -1 | cut -d" " -f3')"
55
61
  elif [ $CHECK = 1 ]; then todo "k3d 설치"
56
62
  else R 'brew install k3d >/tmp/fleet-k3d.log 2>&1' && ok "k3d (설치)" || bad "k3d 설치 실패"; fi
63
+ fi
57
64
  # 6. 클러스터 elanous-pool — API 를 0.0.0.0:<포트> 로 · 인증서 SAN = 호스트 이름 ⊕ tailnet 이름
58
65
  TSNAME="$(R '(tailscale status --self --json 2>/dev/null || /Applications/Tailscale.app/Contents/MacOS/Tailscale status --self --json 2>/dev/null) | python3 -c "import json,sys;print(json.load(sys.stdin)[\"Self\"][\"DNSName\"].rstrip(\".\"))"' 2>/dev/null)"
66
+ if [ "$NODE_OS" = Linux ]; then linux_tsname; fi
59
67
  # 5b. 노드 로컬 레지스트리 — 델타 판올림(대표 2026-09-26): 원격 빌드가 여기로 push 하면 «없는 층만» 올라가고
60
68
  # Pod 는 커밋 태그로 pull 한다(종전 `k3d image import` 는 4GB 전체를 클러스터 저장소로 매번 복사했다).
61
69
  REGISTRY="${ELANOUS_FLEET_REGISTRY:-elanous-registry}"; REG_PORT="${ELANOUS_FLEET_REGISTRY_PORT:-5050}"
@@ -74,9 +82,11 @@ fi
74
82
  # 🪞 호스트 git 미러(대표 09-29) — Pod 는 GitHub 대신 이 미러에서 clone 한다(`ELANOUS_POD_HOST_MIRROR` · #21929).
75
83
  # bare 미러 = 원격 ~/mirror/elanous-agent.git(없으면 만든다 · 갱신은 이 맥의 크론이 push). 클러스터 노드에 읽기 전용으로 붙인다.
76
84
  MIRROR_DIR='$HOME/mirror'; MIRROR_MOUNT=/mirror-host
85
+ if [ "$NODE_OS" = Linux ]; then linux_mirror; else
77
86
  if R "test -d ~/mirror/elanous-agent.git"; then ok "호스트 git 미러 ~/mirror/elanous-agent.git"
78
87
  elif [ $CHECK = 1 ]; then todo "호스트 git 미러 ~/mirror/elanous-agent.git 생성(git init --bare) ⊕ 이 맥에서 첫 push"
79
88
  else R "mkdir -p ~/mirror && git init -q --bare ~/mirror/elanous-agent.git" && ok "호스트 git 미러 (생성 · 첫 push 는 이 맥 크론)" || { bad "미러 생성 실패"; exit 1; }; fi
89
+ fi
80
90
  # 클러스터가 그 레지스트리를 쓰나 ⊕ 미러가 붙어 있나 — 둘 중 하나라도 아니면(옛 클러스터) 돌고 있는 Job 이 없을 때만 다시 만든다.
81
91
  # ⛔ k3d 는 볼륨을 «클러스터 생성 때만» 붙인다 — 붙이려면 다시 만들 수밖에 없다.
82
92
  REG_WIRED=0; R "docker exec k3d-$CLUSTER-server-0 cat /etc/rancher/k3s/registries.yaml 2>/dev/null | grep -q k3d-$REGISTRY" && REG_WIRED=1
@@ -177,6 +187,7 @@ kubectl --context "$CTX" --request-timeout=10s -n elanous-test delete "pod/$DNS_
177
187
  v="$(kubectl --context "$CTX" --request-timeout=10s get nodes -o jsonpath='{.items[0].status.nodeInfo.kubeletVersion}' 2>/dev/null)"
178
188
  want="${K3S_IMAGE##*:}"; want="${want/-k3s/+k3s}"
179
189
  [ "$v" = "$want" ] && ok "k3s $v" || bad "k3s ${v:-응답 없음} ≠ 기대 $want"
190
+ if [ "$NODE_OS" = Linux ]; then linux_handoff; fi
180
191
  echo
181
192
  if [ $FAIL = 0 ] && [ $TODO -gt 0 ]; then echo "→ $HOST 할 일 $TODO — --check 없이 다시 돌리면 채운다"; exit 2
182
193
  elif [ $FAIL = 0 ]; then echo "✓ $HOST 준비됨 — 풀에 넣기: ELANOUS_POD_POOL='…,$CTX@$HOST:<상한>#k3d-$REGISTRY:$REG_PORT'"; else echo "✗ $HOST 미완 — 위 ✗ 를 먼저"; fi
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * ZAI-POD-CHILD (0.2.24) — 호스트 ZAI 키(키 캐시 `~/.cache/zai_api_key` → env `ZAI_API_KEY`) → 풀 클러스터의 glm Pod 전용 Secret(`elanous-zai-coding-plan`).
4
+ *
5
+ * ⛔ 값은 터미널·로그·파일 어디에도 안 낸다 — 키는 메모리에서 바로 `kubectl apply -f -` 의 stdin 으로만 간다. 출력은 «키 이름 · 대상» 뿐이다.
6
+ * ⛔ 기본은 «계획만»(dry-run · 클러스터 접촉 0). 실제로 만들려면 `--apply` — 승인 뒤에만 친다.
7
+ * 본보기 = scripts/fleet/bedrock-pod-secret.ts
8
+ *
9
+ * 사용:
10
+ * bun scripts/fleet/zai-pod-secret.ts --context k3d-elanous-pool # 계획만(키 이름)
11
+ * bun scripts/fleet/zai-pod-secret.ts --context k3d-elanous-pool --apply # 만든다/갱신한다
12
+ * 옵션: --namespace <ns>(기본 elanous-test)
13
+ * 짝: docker/h1/policy-zai.yaml(이그레스) · src/task-orchestrator/surfaces/pod-zai-child.ts(발사 전 판정 · 동시 상한)
14
+ */
15
+ import { spawnSync } from 'node:child_process';
16
+ import { ZAI_POD_SECRET_KEY, ZAI_POD_SECRET_NAME } from '../../src/task-orchestrator/surfaces/pod-zai-child.js';
17
+
18
+ export interface ZaiSecretArgs { context?: string; namespace: string; apply: boolean }
19
+
20
+ export function parseArgs(argv: readonly string[]): ZaiSecretArgs | { error: string } {
21
+ const out: ZaiSecretArgs = { namespace: 'elanous-test', apply: false };
22
+ for (let i = 0; i < argv.length; i++) {
23
+ const a = argv[i]!;
24
+ const value = () => { const v = argv[++i]; if (!v || v.startsWith('--')) throw new Error(`${a} 에 값이 없다`); return v; };
25
+ try {
26
+ if (a === '--context') out.context = value();
27
+ else if (a === '--namespace') out.namespace = value();
28
+ else if (a === '--apply') out.apply = true;
29
+ else return { error: `모르는 인자: ${a}` };
30
+ } catch (e) { return { error: (e as Error).message }; }
31
+ }
32
+ if (!out.context) return { error: '--context <풀 문맥> 이 필요하다 — 어느 클러스터에 둘지 명시한다(기본 문맥에 조용히 쓰지 않는다)' };
33
+ return out;
34
+ }
35
+
36
+ /** Secret 매니페스트 — 값은 이 객체 안에만 있고, 호출자는 stdin 으로만 넘긴다. */
37
+ export function zaiPodSecretManifest(input: { namespace: string; key: string; name?: string }): Record<string, unknown> {
38
+ return {
39
+ apiVersion: 'v1', kind: 'Secret', type: 'Opaque',
40
+ metadata: { name: input.name ?? ZAI_POD_SECRET_NAME, namespace: input.namespace, labels: { 'elanous.secret': 'zai-coding-plan' } },
41
+ stringData: { [ZAI_POD_SECRET_KEY]: input.key },
42
+ };
43
+ }
44
+
45
+ /** 사람에게 보이는 계획 — 키 «이름»만. */
46
+ export function describePlan(args: ZaiSecretArgs): string {
47
+ return [
48
+ `대상: --context ${args.context} · ${args.namespace}/${ZAI_POD_SECRET_NAME}`,
49
+ '출처: 호스트 키 캐시 ~/.cache/zai_api_key → env ZAI_API_KEY',
50
+ `키: ${ZAI_POD_SECRET_KEY} (값은 출력하지 않는다)`,
51
+ ].join('\n');
52
+ }
53
+
54
+ export interface Deps {
55
+ key: () => string | undefined;
56
+ kubectl: (args: string[], stdin: string) => { status: number | null; stdout: string; stderr: string };
57
+ print: (line: string) => void;
58
+ }
59
+
60
+ const defaultDeps: Deps = {
61
+ key: () => undefined,
62
+ kubectl: (args, stdin) => {
63
+ const r = spawnSync('kubectl', args, { input: stdin, encoding: 'utf8', timeout: 60_000 });
64
+ return { status: r.status, stdout: r.stdout ?? '', stderr: r.stderr ?? '' };
65
+ },
66
+ print: (line) => console.log(line),
67
+ };
68
+
69
+ export async function run(argv: readonly string[], deps: Deps = defaultDeps): Promise<number> {
70
+ const args = parseArgs(argv);
71
+ if ('error' in args) { deps.print(`⛔ ${args.error}`); return 2; }
72
+ const key = deps.key()?.trim();
73
+ if (!key) { deps.print('⛔ 호스트에 ZAI 키가 없다 — ~/.cache/zai_api_key 또는 env ZAI_API_KEY'); return 2; }
74
+ deps.print(describePlan(args));
75
+ if (!args.apply) { deps.print('계획만(dry-run) — 클러스터에 쓰지 않았다. 만들려면 --apply(승인 뒤)'); return 0; }
76
+ const r = deps.kubectl(['--context', args.context!, 'apply', '-f', '-'], JSON.stringify(zaiPodSecretManifest({ namespace: args.namespace, key })));
77
+ // kubectl apply 의 출력은 «secret/<이름> created|configured» 뿐이다 — stderr 는 원문 대신 rc 만.
78
+ if (r.status !== 0) { deps.print(`⛔ kubectl apply 실패 rc=${r.status}`); return 1; }
79
+ deps.print(`✅ ${r.stdout.trim().split('\n')[0] ?? 'applied'}`);
80
+ return 0;
81
+ }
82
+
83
+ if (import.meta.main) {
84
+ const { getZaiApiKey } = await import('../../src/llm/zai-coding-plan.js');
85
+ process.exit(await run(process.argv.slice(2), { ...defaultDeps, key: () => getZaiApiKey() }));
86
+ }
@@ -25,6 +25,9 @@ export const LAND_GLOBAL_CHECKS: readonly LandGlobalCheck[] = [
25
25
  { pattern: /^docs\/ops\/machines\.yaml$/, test: 'test/machines-ledger-data.test.ts', why: '기계 원장 데이터 ↔ 생성 Markdown(#24729)' },
26
26
  // 새 CLI 하위 명령은 `src/index.ts` 또는 `src/cli/` 의 등록 모듈에서 생긴다 — 입구 목록 시험(#24661).
27
27
  { pattern: /^src\/(?:index|cli\/(?:[^/]+\/)*[^/]+)(?<!\.test)\.ts$/, test: 'src/index.test.ts', why: 'CLI 입구·하위 명령 목록(#24661)' },
28
+ // src/cli/ 밖의 명령 등록 모듈(`*-cli.ts` · `*-cli-command.ts`)도 하위 명령을 더한다 — 10-11 #26232 가
29
+ // src/harness/harness-cli-command.ts 에 `harness progress` 를 더해 index.test 목록 스냅샷을 깨고 main 을 붉혔다.
30
+ { pattern: /^src\/(?:[^/]+\/)+[^/]+-cli(?:-command)?(?<!\.test)\.ts$/, test: 'src/index.test.ts', why: 'src/cli 밖 명령 등록 모듈의 하위 명령 목록(10-11 #26232)' },
28
31
  ];
29
32
 
30
33
  /** 바뀐 파일 → 끌려오는 전역 시험(표 순서 · 중복 없음). */
@@ -0,0 +1,213 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * LOG-DIET-2 (0.2.24) — «같은 부하»에서 관측 «행 수»가 얼마나 줄었나를 재생(replay)으로 잰다.
4
+ *
5
+ * 왜 재생인가: 판정선은 «같은 부하에서 시간당 행 −60%»인데 72분 실물 런은 이 칸의 창에 들어가지
6
+ * 않는다. 그래서 칸 문면이 «측정값»으로 준 부하를 기록열로 재구성하고, 그 «같은» 기록열을
7
+ * 0.2.23 정책과 현재 정책 두 가드에 흘려 내보낸 행을 센다. 측정은 «정책 차이»이지 실물 재현이
8
+ * 아니다 — 이 한계를 출력에도 적는다.
9
+ *
10
+ * 실행: bun scripts/measure-log-diet-2.ts [--json]
11
+ * 계약: bun test scripts/measure-log-diet-2.test.ts
12
+ */
13
+ import { LOG_DIET_KEYS, LogDietGuard } from '../src/debug/log-diet.js';
14
+ import type { DebugEvent } from '../src/debug/log.js';
15
+
16
+ /** 0.2.23 절식 정책 — A/B «기준선»으로 얼려 둔 사본(현재 `LOG_DIET_KEYS` 와 의도적으로 다르다). */
17
+ export const LOG_DIET_KEYS_0_2_23: Readonly<Record<string, 'repeat' | 'consecutive'>> = {
18
+ 'watchdog/activity': 'consecutive',
19
+ 'user-config.llm/provider-resolved': 'repeat',
20
+ 'hitl.card-bridge/skipped': 'repeat',
21
+ 'input.cursor.claim/chat-main': 'repeat',
22
+ 'tox.loop/waiting-capacity': 'repeat',
23
+ 'tox.loop/tick': 'repeat',
24
+ };
25
+
26
+ /** 관측된 `log.diet/suppressed` 4,313행(72분) ≈ 초당 1회 flush — 그 주기를 기준선으로 쓴다. */
27
+ export const REPLAY_FLUSH_EVERY_MS = 1_000;
28
+ export const REPLAY_WINDOW_MS = 72 * 60_000;
29
+
30
+ export type ReplayRecord = Pick<DebugEvent, 'category' | 'event' | 'data' | 'level'> & { atMs: number };
31
+
32
+ export type LoadGroup = {
33
+ /** `category/event` */
34
+ key: string;
35
+ level?: DebugEvent['level'];
36
+ /** 창(72분) 동안 관측된 행 수 */
37
+ rows: number;
38
+ /** 서로 다른 payload 수 — 1 이면 전부 한 스트림으로 접힌다 */
39
+ distinctValues: number;
40
+ source: 'measured' | 'assumed';
41
+ note: string;
42
+ };
43
+
44
+ /** 판정 부하: 칸 문면이 수로 준 군 ⊕ 판정 경로(미등록 키) 0-손실 대조군. */
45
+ export const LOG_DIET_2_LOAD: readonly LoadGroup[] = [
46
+ {
47
+ key: 'llm.credential/provider-key-missing', level: 'warn', rows: 5_207, distinctValues: 1,
48
+ source: 'measured', note: '칸 문면 측정값 — 72분 5,207행, 같은 (provider, source, context)',
49
+ },
50
+ {
51
+ key: 'log-diet.replay/judgment-path', rows: 300, distinctValues: 300,
52
+ source: 'assumed', note: '합성 대조군 — 미등록(판정 경로) 키는 100% 통과해야 한다',
53
+ },
54
+ ];
55
+
56
+ export type ReplayResult = {
57
+ rowsIn: number;
58
+ /** 통과한 원본 행 + `log.diet/suppressed` 요약 행 */
59
+ rowsOut: number;
60
+ originalRows: number;
61
+ summaryRows: number;
62
+ /** 요약 행이 실어 나른 접힌 기록의 총수 — `originalRows + suppressedTotal === rowsIn` 이어야 한다 */
63
+ suppressedTotal: number;
64
+ judgmentRowsIn: number;
65
+ judgmentRowsOut: number;
66
+ judgmentLoss: number;
67
+ /** 1 − rowsOut/rowsIn */
68
+ reductionRatio: number;
69
+ /** 참고값 — 내보낸 행 중 `log.diet` 요약 행의 비중(0~1). 판정 ② «총/목록 밖 비»가 «아니다» — 그 비는 재생으로 못 잰다. */
70
+ summaryShare: number;
71
+ };
72
+
73
+ export function isJudgmentPath(rec: Pick<DebugEvent, 'category' | 'event'>): boolean {
74
+ return rec.category !== 'log.diet' && !LOG_DIET_KEYS[`${rec.category}/${rec.event}`];
75
+ }
76
+
77
+ /** 같은 기록열을 두 정책(기준선/현재)에 흘릴 수 있게 군을 시간순 기록열로 펼친다. */
78
+ export function buildReplayRecords(
79
+ groups: readonly LoadGroup[],
80
+ windowMs = REPLAY_WINDOW_MS,
81
+ ): ReplayRecord[] {
82
+ const records: ReplayRecord[] = [];
83
+ for (const group of groups) {
84
+ const [category, event] = group.key.split('/');
85
+ if (!category || !event) throw new Error(`잘못된 부하 키: ${group.key}`);
86
+ const step = group.rows > 0 ? windowMs / group.rows : windowMs;
87
+ for (let i = 0; i < group.rows; i++) {
88
+ const seq = group.distinctValues > 1 ? i % group.distinctValues : 0;
89
+ records.push({
90
+ atMs: Math.round(i * step),
91
+ category,
92
+ event,
93
+ data: { key: group.key, seq },
94
+ ...(group.level ? { level: group.level } : {}),
95
+ });
96
+ }
97
+ }
98
+ return records.sort((a, b) => a.atMs - b.atMs);
99
+ }
100
+
101
+ export function replayDiet(
102
+ records: readonly ReplayRecord[],
103
+ makeGuard: (now: () => number) => LogDietGuard,
104
+ flushEveryMs = REPLAY_FLUSH_EVERY_MS,
105
+ ): ReplayResult {
106
+ let clock = 0;
107
+ const guard = makeGuard(() => clock);
108
+ let originalRows = 0;
109
+ let summaryRows = 0;
110
+ let suppressedTotal = 0;
111
+ let judgmentRowsIn = 0;
112
+ let judgmentRowsOut = 0;
113
+ let nextFlush = flushEveryMs;
114
+ for (const rec of records) {
115
+ while (nextFlush <= rec.atMs) {
116
+ clock = nextFlush;
117
+ const released = guard.flushPending();
118
+ summaryRows += released.length;
119
+ suppressedTotal += released.reduce((sum, s) => sum + s.count, 0);
120
+ nextFlush += flushEveryMs;
121
+ }
122
+ clock = rec.atMs;
123
+ const judgment = isJudgmentPath(rec);
124
+ if (judgment) judgmentRowsIn++;
125
+ const decision = guard.admit(rec);
126
+ if (decision.pass) {
127
+ originalRows++;
128
+ if (judgment) judgmentRowsOut++;
129
+ }
130
+ summaryRows += decision.summaries.length;
131
+ suppressedTotal += decision.summaries.reduce((sum, s) => sum + s.count, 0);
132
+ }
133
+ clock = (records.at(-1)?.atMs ?? 0) + flushEveryMs;
134
+ const drained = guard.drain();
135
+ summaryRows += drained.length;
136
+ suppressedTotal += drained.reduce((sum, s) => sum + s.count, 0);
137
+ const rowsOut = originalRows + summaryRows;
138
+ return {
139
+ rowsIn: records.length,
140
+ rowsOut,
141
+ originalRows,
142
+ summaryRows,
143
+ suppressedTotal,
144
+ judgmentRowsIn,
145
+ judgmentRowsOut,
146
+ judgmentLoss: judgmentRowsIn - judgmentRowsOut,
147
+ reductionRatio: records.length === 0 ? 0 : 1 - rowsOut / records.length,
148
+ summaryShare: rowsOut === 0 ? 0 : summaryRows / rowsOut,
149
+ };
150
+ }
151
+
152
+ export type Diet2Measurement = {
153
+ windowMinutes: number;
154
+ flushEveryMs: number;
155
+ records: number;
156
+ groups: readonly LoadGroup[];
157
+ /** 0.2.23 정책(기준선) */
158
+ before: ReplayResult;
159
+ /** 현재 정책(LOG-DIET-2) */
160
+ after: ReplayResult;
161
+ /** 1 − after.rowsOut / before.rowsOut — 판정선 −60% 가 무는 값 */
162
+ reduction: number;
163
+ };
164
+
165
+ export function measureLogDiet2(
166
+ groups: readonly LoadGroup[] = LOG_DIET_2_LOAD,
167
+ windowMs = REPLAY_WINDOW_MS,
168
+ ): Diet2Measurement {
169
+ const records = buildReplayRecords(groups, windowMs);
170
+ const before = replayDiet(records, now => new LogDietGuard({
171
+ now,
172
+ keys: LOG_DIET_KEYS_0_2_23,
173
+ summaryMinIntervalMs: 0,
174
+ summaryMaxPending: 1,
175
+ }));
176
+ const after = replayDiet(records, now => new LogDietGuard({ now }));
177
+ return {
178
+ windowMinutes: Math.round(windowMs / 60_000),
179
+ flushEveryMs: REPLAY_FLUSH_EVERY_MS,
180
+ records: records.length,
181
+ groups,
182
+ before,
183
+ after,
184
+ reduction: before.rowsOut === 0 ? 0 : 1 - after.rowsOut / before.rowsOut,
185
+ };
186
+ }
187
+
188
+ const pct = (ratio: number): string => `${(ratio * 100).toFixed(1)}%`;
189
+
190
+ function report(m: Diet2Measurement): string {
191
+ const lines: string[] = [
192
+ `LOG-DIET-2 재생 측정 — 창 ${m.windowMinutes}분 · flush ${m.flushEveryMs}ms · 기록 ${m.records}행`,
193
+ '⚠ 정책 차이의 재생 측정이다(72분 실물 런 아님). 부하 군:',
194
+ ...m.groups.map(g => ` - ${g.key} ${g.rows}행/${g.distinctValues}값 [${g.source}] ${g.note}`),
195
+ '',
196
+ `기준선(0.2.23) 행 ${m.before.rowsOut} (원본 ${m.before.originalRows} + 요약 ${m.before.summaryRows} · 접힌 기록 ${m.before.suppressedTotal})`,
197
+ `현재(LOG-DIET-2) 행 ${m.after.rowsOut} (원본 ${m.after.originalRows} + 요약 ${m.after.summaryRows} · 접힌 기록 ${m.after.suppressedTotal})`,
198
+ `보존 : 원본+접힌 기록 = 입력 → ${m.after.originalRows}+${m.after.suppressedTotal}=${m.records} ${m.after.originalRows + m.after.suppressedTotal === m.records ? 'PASS' : 'FAIL'}`,
199
+ '',
200
+ `판정 ① 같은 부하 시간당 행 −60% : ${pct(m.reduction)} 감소 → ${m.reduction >= 0.6 ? 'PASS' : 'FAIL'}`,
201
+ `판정 ② 총/목록 밖 비 ≤ 1.03 : 재생으로 못 잰다(unmeasured · 실물 logs 목록 대비 필요) — 참고: 요약 행 비중 ${m.after.summaryShare.toFixed(3)}`,
202
+ `판정 ③ 판정 경로 이벤트 0 손실 : ${m.after.judgmentRowsOut}/${m.after.judgmentRowsIn} 통과 (손실 ${m.after.judgmentLoss}) → ${m.after.judgmentLoss === 0 ? 'PASS' : 'FAIL'}`,
203
+ ];
204
+ return lines.join('\n');
205
+ }
206
+
207
+ if (import.meta.main) {
208
+ const m = measureLogDiet2();
209
+ if (process.argv.includes('--json')) console.log(JSON.stringify(m, null, 2));
210
+ else console.log(report(m));
211
+ const ok = m.reduction >= 0.6 && m.after.judgmentLoss === 0; // 판정 ② 는 여기서 판정하지 않는다(unmeasured)
212
+ process.exit(ok ? 0 : 1);
213
+ }