elanous 0.2.15 → 0.2.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/{1307-33ffcc49ab45123b.js → 1307-aac7332576c4d6fc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{6877-7c6208705c284124.js → 6877-fe94e76a5841ee9b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8401-4ce1c42c8d89903f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/899-f41be85e8a680651.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9103-7a3ae7f60af3b99f.js → 9103-60fbef33e66f0a46.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9247-269e4464cc4b491c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/{page-0e2c57fe6add4b2a.js → page-3143b07a817d1da9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-e05a77df7e005b04.js → page-17ba086ef82d339b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-7eb661ff23af7da2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-6b8444914da74ca6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-b8c49f6b4fa2df91.js → page-6e5c3a867557298b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-7110f732f45bf944.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-976813c91d7394ea.js → page-9a41d65355fc25b9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-5e77cd1802567c89.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-1d4901eb7c2d8822.js → page-41afec0b255ed2bb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-72d3e7ff35a8c954.js → page-4187666132fc98c4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-60c7a8e1a4fe62ce.js → page-09328d499162bae4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-2a52b316cb143d9c.js → page-29ff3a8f84f03b6a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-c3ce483c8d5db370.js → page-80ec7401bc83f511.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-ab93b240f687d69a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-96f074f6538e8a2c.js → page-fc7d86467291ae38.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{layout-e97be5334d83a9f5.js → layout-de43a186e5ba5683.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-8b08cafad27759c9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-0e2d6985fb42cd2f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-13e4108999f58d13.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-e8296ee284aa1293.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-c1343f9499d86f3f.js → page-043a9111e74f6b24.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-8739a2e0ae793130.js → not-found-23a4601ceb7f452e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-1ed1324b1dc36754.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-d23b12bb92242558.js → page-10d958605a77b3bd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-166745e51ac0c7b0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-c7fea475488201a8.js → page-a04260b8ccc968d4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-7a1d85e121065cc8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-1f50e3d4e13435f4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-5efbf81124c474ae.js → page-12916918988ac97e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-d8a3347a9d0c3f4a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-a9117d2cd3093d36.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-06ee266953d9c879.js → page-5a64cb5208c6a8fd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-153c2b3bcc89e8de.js → page-1407ec83ea64c1f9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-1d824510df025d18.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-6e878ebbe1737d00.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/{page-0d6d23b40263a7ea.js → page-7f49fe645d5132f7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-81a4e7f8dbfe74cb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-27d71f131acd9320.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-3ab59d19913b200e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-bf85b2f5bf077925.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/{page-73735b64bfe1ae67.js → page-424659bacfb8cdd7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-ab32a8f522a60371.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-f7e631e9b4daf66e.js → page-fc2df261f8a2c7df.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-09461366af82ef13.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-9817c7f9007466cc.js → page-5d4fef9ccd7d406b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ea90a8ac73ce2749.js +1 -0
- package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +1 -0
- package/apps/pwa/out/_next/static/css/{04dabcbcc4bfa6db.css → 085673d826b98b62.css} +1 -1
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +15 -15
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +15 -15
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +15 -15
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +15 -15
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +15 -15
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +15 -15
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +15 -15
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +15 -15
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +15 -15
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +15 -15
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +15 -15
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +15 -15
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +15 -15
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +15 -15
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +15 -15
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +15 -15
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +15 -15
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +15 -15
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +15 -15
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +15 -15
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +15 -15
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +15 -15
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +15 -15
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +15 -15
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +15 -15
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +15 -15
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +16 -16
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +15 -15
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +15 -15
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +15 -15
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +15 -15
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +15 -15
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +15 -15
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +15 -15
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +15 -15
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +15 -15
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +15 -15
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +15 -15
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +15 -15
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +15 -15
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +15 -15
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +15 -15
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +15 -15
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +15 -15
- package/graphs/doc-gen/doc-gen.yaml +24 -0
- package/graphs/doc-gen/recipes.yaml +3 -0
- package/graphs/intake/intake-daily.yaml +9 -2
- package/graphs/intake/news-sources.yaml +4 -0
- package/graphs/intake/recipes.yaml +6 -1
- package/graphs/orchestrator/orchestrator.yaml +7 -0
- package/graphs/orchestrator/recipes.yaml +3 -0
- package/graphs/steward/recipes.yaml +2 -1
- package/package.json +1 -1
- package/scripts/__fixtures__/intake-news.xml +7 -0
- package/scripts/ci-public-export-import-gate.ts +154 -0
- package/scripts/ci-test-interference-gate.ts +5 -1
- package/scripts/ci-typecheck-changed.ts +34 -15
- package/scripts/docs/doc-rot.ts +164 -0
- package/scripts/docs/generate-graph-anatomy.ts +131 -9
- package/scripts/docs-cli-check.ts +4 -4
- package/scripts/goals/goal-census.ts +343 -0
- package/scripts/intake-news.ts +169 -0
- package/scripts/measure/author-section-contribution.ts +84 -0
- package/scripts/release-loop/cut-branch.ts +43 -13
- package/scripts/release-loop/docs-land-node.ts +69 -10
- package/scripts/release-loop/gate-node.ts +3 -1
- package/scripts/release-loop/publish-node.ts +2 -2
- package/scripts/release-loop/release-docs.ts +134 -0
- package/scripts/release-loop/vault-note-node.ts +19 -2
- package/scripts/release-loop/version-node.ts +7 -4
- package/scripts/release-story/draft.ts +42 -14
- package/scripts/rhythm/monthly.ts +97 -0
- package/src/acp/client.ts +17 -2
- package/src/agent-mission/driver.ts +104 -11
- package/src/agent-mission/supervisor-metrics-cli.ts +40 -0
- package/src/agent-mission/supervisor-metrics.ts +218 -0
- package/src/autopilot/track-agent-shadow.ts +2 -1
- package/src/autopilot/track-agent.ts +5 -3
- package/src/briefing/brief-items.ts +191 -36
- package/src/budget/usage-outcomes.ts +204 -0
- package/src/cli/autopilot-cli.ts +66 -0
- package/src/cli/brief-cli.ts +73 -0
- package/src/cli/decisions-cli.ts +4 -1
- package/src/cli/intake-cli.ts +1 -1
- package/src/cli/know-cli.ts +20 -0
- package/src/cli/log-event-names.ts +1 -0
- package/src/cli/logs-cli.ts +43 -1
- package/src/cli/pr-cli.ts +92 -13
- package/src/cli/provider-cli.ts +14 -0
- package/src/cli/release-cli.ts +11 -8
- package/src/cli/self-update-canary.ts +46 -0
- package/src/cli/self-update.ts +399 -1
- package/src/cli/usage-cli.ts +26 -0
- package/src/context-card/index.ts +45 -0
- package/src/context-card/task.ts +76 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +2 -0
- package/src/decisions/decision-ledger.ts +10 -1
- package/src/discord-self-message.ts +9 -0
- package/src/flow-loop/split.ts +24 -0
- package/src/harness/harness-cli-command.ts +178 -12
- package/src/harness/harness-pod-dispatch.ts +36 -55
- package/src/harness/harness-queue.ts +86 -7
- package/src/harness/harness-seams.ts +13 -3
- package/src/harness/helper-repair.ts +139 -8
- package/src/harness/helper-scan.ts +13 -2
- package/src/harness/nested-elanous-policy.ts +133 -0
- package/src/hq/fence-wrapper.ts +123 -0
- package/src/hq/hq.ts +16 -2
- package/src/hq/lease.ts +5 -3
- package/src/hq/transfer.ts +185 -0
- package/src/index.ts +168 -13
- package/src/intake-plane/digest.ts +85 -28
- package/src/knowledge/know-find.ts +105 -0
- package/src/llm.ts +75 -19
- package/src/loops/orchestrator/finish-rate.ts +14 -3
- package/src/loops/orchestrator/tick.ts +96 -25
- package/src/maturity/bot-slash-catalog.ts +11 -2
- package/src/maturity/feature-maturity.ts +2 -2
- package/src/maturity/tui-slash-availability.ts +9 -0
- package/src/nexus/api/grid.ts +61 -0
- package/src/nexus/api/http-server.ts +19 -1
- package/src/nexus/api/task-cards-api.ts +19 -0
- package/src/oauth/codex.ts +82 -2
- package/src/oauth/overload-failover-launch.ts +75 -0
- package/src/oauth/overload-failover.ts +137 -0
- package/src/release-loop/checklist.ts +10 -5
- package/src/release-loop/feature-store.ts +10 -9
- package/src/release-loop/landing-freeze.ts +18 -10
- package/src/release-loop/placement.ts +17 -8
- package/src/seat-dispatch/seat-ask.ts +33 -6
- package/src/seat-loop/seat-loop.ts +172 -21
- package/src/self-dev/ask-launch-flow.ts +48 -8
- package/src/self-dev/authored-goal-trace.ts +64 -0
- package/src/self-dev/dev-cli.ts +19 -1
- package/src/self-dev/dev-pipeline.ts +6 -5
- package/src/self-dev/entrance-registry.ts +10 -6
- package/src/self-dev/old-door.ts +73 -0
- package/src/self-dev/run-store.ts +5 -1
- package/src/self-dev/run-supervisor.ts +7 -1
- package/src/self-dev/supervisor-verdict-edges.ts +46 -0
- package/src/self-implement/app-api-budget.ts +166 -0
- package/src/self-implement/budget-gate.ts +7 -5
- package/src/self-implement/follow-up-goals.ts +289 -0
- package/src/self-implement/frozen-merges.ts +25 -20
- package/src/self-implement/goal-author-clarification.ts +81 -0
- package/src/self-implement/goal-author-cli.ts +33 -5
- package/src/self-implement/goal-author.ts +107 -25
- package/src/self-implement/goal-clarification-escalation.ts +37 -1
- package/src/self-implement/goal-document-write.ts +28 -0
- package/src/self-implement/heal-intake.ts +4 -0
- package/src/self-implement/heal-triage.ts +44 -0
- package/src/self-implement/host-regate.ts +3 -1
- package/src/self-implement/orchestrator.ts +157 -8
- package/src/self-implement/run-ledger.ts +4 -0
- package/src/self-implement/seams.ts +44 -15
- package/src/session-runtime/index.ts +2 -0
- package/src/session-runtime/retry-api.ts +87 -0
- package/src/session-runtime/retry-policy.ts +107 -6
- package/src/steward/working-backwards.ts +16 -1
- package/src/task-agent/actions.ts +118 -0
- package/src/task-agent/judge.ts +96 -0
- package/src/task-cards/card-cli.ts +25 -0
- package/src/task-cards/card-store.ts +9 -4
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +33 -4
- package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +4 -3
- package/src/task-orchestrator/surfaces/pod-ledger-prod-sink.ts +75 -0
- package/src/task-orchestrator/surfaces/pod-pool.ts +95 -18
- package/src/task-orchestrator/surfaces/pod-source-receive.ts +37 -1
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +168 -37
- package/src/task-orchestrator/surfaces/self-implement.ts +2 -0
- package/src/telegram-commands.ts +6 -1
- package/src/telegram-loops-command.ts +21 -14
- package/src/telegram.ts +47 -0
- package/src/user-config.ts +59 -4
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9247-29d889b809288e65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-f544338193a9a762.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-5491c115b1f9a923.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-96398e364fbd4548.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-28b9448091776c06.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-13c7b53e0c2519a9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-65e484302921a932.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-34d08facaf08f556.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-955ec2da3be4c560.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-9f88fbc15d9058d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-c9fc5706b8abaa11.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8d47f45a0dc044ac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-e2ee2896811e98d0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-83b19dae8619de05.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-85f1a1fc0a82738c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-da6c08aae0f92768.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-87a11e6a05f15199.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-7d4182964407c09c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-7e8c39241f8eb6d0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-008c1a96618a4887.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-f5597e894c02cd33.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-0337fdf7fb5e37b6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-8fcec4c9ea2e7f70.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-4881b8f9f63694b4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-97f9f43172a3a90e.js +0 -1
- package/apps/pwa/out/_next/static/css/1f7c958c41ffa3b9.css +0 -1
- package/src/launch-head/seat-goal-queue-lock.fixture.ts +0 -15
- package/src/launch-head/seat-goal-queue.ts +0 -198
- /package/apps/pwa/out/_next/static/{LZcstaO9_NXypPFmujolv → yRA5ur6DcPl0YHSRheH2O}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{LZcstaO9_NXypPFmujolv → yRA5ur6DcPl0YHSRheH2O}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { debug } from '../../debug/log.js';
|
|
2
|
+
import { LogStore, logsDbPath } from '../../mss/logging/log-store.js';
|
|
3
|
+
|
|
4
|
+
type Emit = (category: string, event: string, data: Record<string, unknown>) => void;
|
|
5
|
+
type Store = Pick<LogStore, 'insertBatch'>;
|
|
6
|
+
|
|
7
|
+
/** 발사한 부모(`harness say --config-dir …`)가 정한 집의 logs.db — 부모가 자식 `self orchestrate` 에 env 로 넘긴다. */
|
|
8
|
+
export const POD_REEMIT_LOGS_DB_ENV = 'ELANOUS_POD_REEMIT_LOGS_DB';
|
|
9
|
+
export function launchHomeLogsDb(env: NodeJS.ProcessEnv = process.env): string | undefined {
|
|
10
|
+
return env[POD_REEMIT_LOGS_DB_ENV]?.trim() || undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let cached: { path: string; store: Store } | null = null;
|
|
14
|
+
let announced = '';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* POD-OBS(10-06 · 0.2.16 P0 · OP 사양): Pod 자식 원장 «재방출»만 부모가 발사 때 정한 집(A)의 logs.db 로 보낸다.
|
|
18
|
+
* 자식 orchestrate 는 트리 파생 test 우주(B)에서 돌아, 지금은 재방출이 B 에만 쌓였다(24h `ledger-collected` 운영 2 vs 다른 우주 250).
|
|
19
|
+
* 자식 우주 원장·격리는 그대로 둔다. A 를 못 열면 조용히 버리지 않고 B 로 떨어뜨리며 경고한다.
|
|
20
|
+
* env 가 없거나(옛 발사) A == B 이거나 시험 프로세스면 null → 호출측이 지금처럼 B 에만 쓴다.
|
|
21
|
+
*/
|
|
22
|
+
export function operationalReemit(opts: {
|
|
23
|
+
currentDb?: string;
|
|
24
|
+
operationalDb?: string;
|
|
25
|
+
open?: (path: string) => Store;
|
|
26
|
+
testProcess?: boolean;
|
|
27
|
+
fallback?: Emit;
|
|
28
|
+
} = {}): Emit | null {
|
|
29
|
+
const operationalDb = opts.operationalDb ?? launchHomeLogsDb();
|
|
30
|
+
if (!operationalDb) return null;
|
|
31
|
+
const currentDb = opts.currentDb ?? logsDbPath();
|
|
32
|
+
const testProcess = opts.testProcess ?? (process.env.NODE_ENV === 'test' || !!process.env.ELANOUS_TEST_HOME);
|
|
33
|
+
if (testProcess && !opts.open) return null;
|
|
34
|
+
if (currentDb === operationalDb) return null;
|
|
35
|
+
const open = opts.open ?? ((path: string) => new LogStore(path, { instance: 'prod' }));
|
|
36
|
+
const fallback: Emit = opts.fallback ?? ((c, e, d) => debug.log(c, e, d));
|
|
37
|
+
const write = (category: string, event: string, data: Record<string, unknown>): void => {
|
|
38
|
+
if (!cached || cached.path !== operationalDb || opts.open) cached = { path: operationalDb, store: open(operationalDb) };
|
|
39
|
+
cached.store.insertBatch([{ rec: { ts: new Date().toISOString(), category, event, data }, surface: 'pod-reemit' }]);
|
|
40
|
+
};
|
|
41
|
+
let warned = false;
|
|
42
|
+
return (category, event, data) => {
|
|
43
|
+
try {
|
|
44
|
+
if (announced !== `${currentDb}->${operationalDb}` || opts.open) {
|
|
45
|
+
// 어느 우주로 보냈는지 한 줄(A 에) — 조회자가 «B 에 왜 없나»를 여기서 본다.
|
|
46
|
+
write('self-implement.pod', 'reemit-target', { targetUniverse: operationalDb, childUniverse: currentDb });
|
|
47
|
+
announced = `${currentDb}->${operationalDb}`;
|
|
48
|
+
}
|
|
49
|
+
write(category, event, data);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
fallback(category, event, data);
|
|
52
|
+
if (warned) return;
|
|
53
|
+
// 경고는 싱크(발사 하나)당 한 번 — 줄마다 찍으면 경고가 원장을 덮는다.
|
|
54
|
+
warned = true;
|
|
55
|
+
try {
|
|
56
|
+
fallback('self-implement.pod', 'reemit-target-unavailable', {
|
|
57
|
+
targetUniverse: operationalDb, childUniverse: currentDb, reason: error instanceof Error ? error.message : String(error),
|
|
58
|
+
});
|
|
59
|
+
} catch { /* nothing */ }
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let shared: { key: string; emit: Emit } | null = null;
|
|
65
|
+
|
|
66
|
+
/** 재방출 기본 경로: 집(A)이 정해져 있으면 A 에만, 아니면 지금처럼 지금 우주(B)의 debug.log.
|
|
67
|
+
* 프로세스(발사)당 싱크 하나 — collector 의 `log`·`emit`, follower 가 같은 싱크를 써서 경고·대상 줄이 한 번이다. */
|
|
68
|
+
export function defaultPodReemit(): Emit {
|
|
69
|
+
const key = `${launchHomeLogsDb() ?? ''}->${logsDbPath()}`;
|
|
70
|
+
if (shared?.key === key) return shared.emit;
|
|
71
|
+
const operational = operationalReemit();
|
|
72
|
+
const emit: Emit = operational ?? ((category, event, data) => { debug.log(category, event, data); });
|
|
73
|
+
shared = { key, emit };
|
|
74
|
+
return emit;
|
|
75
|
+
}
|
|
@@ -8,13 +8,14 @@
|
|
|
8
8
|
* - 컨텍스트 kubectl 컨텍스트 이름(이 맥의 kubeconfig)
|
|
9
9
|
* - @ssh호스트 원격 클러스터면 그 기계 — 이미지 판 대조·반입(`docker load` ⊕ `k3d image import`)에 쓴다
|
|
10
10
|
* - 상한 이 노드에 동시에 둘 Job 수(기본 2)
|
|
11
|
-
* Job 마다 앞
|
|
11
|
+
* Job 마다 «빈 자리 비율»이 가장 큰 노드를 고른다(동률은 앞 순위 · 측정 실패 노드는 측정된 노드가 다 찼을 때만). 다 차면 자리가 날 때까지 기다린다.
|
|
12
12
|
* 노드 선택의 진행 중 수는 로컬 프로세스 기준이다. 발사 허가는 별도의 lease 측정으로
|
|
13
13
|
* 다른 호스트의 Running/Pending 및 메모리 예약까지 고려한다.
|
|
14
14
|
* 풀을 안 주면 종전처럼 «현재 컨텍스트» 하나다(동작 불변).
|
|
15
15
|
*/
|
|
16
16
|
import { spawn, spawnSync } from 'node:child_process';
|
|
17
|
-
import { existsSync } from 'node:fs';
|
|
17
|
+
import { existsSync, mkdtempSync, rmSync } from 'node:fs';
|
|
18
|
+
import { tmpdir } from 'node:os';
|
|
18
19
|
import { join, resolve } from 'node:path';
|
|
19
20
|
import { debug } from '../../debug/log.js';
|
|
20
21
|
import { PodLeaseAdmission, type PodLeasePredecessor, type PodLeaseRelease } from '../../pod-lease/admission.js';
|
|
@@ -186,7 +187,8 @@ export class PodPoolScheduler {
|
|
|
186
187
|
private readonly hostLease: HostPoolLease;
|
|
187
188
|
private readonly pollMs: number;
|
|
188
189
|
private readonly occupancy: () => Record<string, MemberOccupancy> | Promise<Record<string, MemberOccupancy>>;
|
|
189
|
-
|
|
190
|
+
private readonly memberMemory: () => Array<{ context: string; allocatableMemoryBytes: number | null }> | Promise<Array<{ context: string; allocatableMemoryBytes: number | null }>>;
|
|
191
|
+
constructor(readonly members: readonly PodPoolMember[], options: { status?: () => PoolLeaseRecommendation | Promise<PoolLeaseRecommendation>; kubectl?: PoolKubectl; dns?: (context: string) => PoolDnsProbe; pollMs?: number; hostLease?: HostPoolLease; memberMemory?: () => Array<{ context: string; allocatableMemoryBytes: number | null }> | Promise<Array<{ context: string; allocatableMemoryBytes: number | null }>>; occupancy?: () => Record<string, MemberOccupancy> | Promise<Record<string, MemberOccupancy>>; dependencyMerged?: (after: PodLeasePredecessor, signal?: AbortSignal) => PredecessorState | boolean | Promise<PredecessorState | boolean> } = {}) {
|
|
190
192
|
this.pollMs = options.pollMs ?? 15_000;
|
|
191
193
|
// One lease directory per pool (contexts, not caps) — every CLI process launching into the same clusters shares it.
|
|
192
194
|
this.hostLease = options.hostLease ?? podPoolHostLease(members);
|
|
@@ -195,6 +197,9 @@ export class PodPoolScheduler {
|
|
|
195
197
|
return recommendConcurrency(measure, { capacity: members.reduce((n, m) => n + m.capacity, 0), accounts: 0, perAccount: 0 });
|
|
196
198
|
});
|
|
197
199
|
this.measure = async () => measureStatus();
|
|
200
|
+
// An injected status (tests, callers with their own measurement) without memberMemory never shells out to kubectl here.
|
|
201
|
+
this.memberMemory = options.memberMemory ?? (options.status ? (() => []) : () => measurePoolLease(members, { ...(options.kubectl ? { kubectl: options.kubectl } : {}), ...(options.dns ? { dns: options.dns } : {}) }).members
|
|
202
|
+
.map((m: PodLeaseMember) => ({ context: m.context, allocatableMemoryBytes: m.allocatableMemoryBytes })));
|
|
198
203
|
this.occupancy = options.occupancy ?? (() => measureMemberOccupancy(members, { ...(options.kubectl ? { kubectl: options.kubectl } : {}), ...(options.dns ? { dns: options.dns } : {}) }));
|
|
199
204
|
this.admission = new PodLeaseAdmission({ status: async () => {
|
|
200
205
|
const measured = await measureStatus();
|
|
@@ -267,15 +272,34 @@ export class PodPoolScheduler {
|
|
|
267
272
|
};
|
|
268
273
|
});
|
|
269
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* LAUNCH-STALL (10-06): members whose total allocatable memory is below one Job's limit can never take it —
|
|
277
|
+
* waiting for them is an endless silent stall (minio 12.5GB vs a 16Gi standard Job). A member whose memory
|
|
278
|
+
* could not be read is not judged unfit (unknown ≠ too small).
|
|
279
|
+
*/
|
|
280
|
+
async unfitMembers(limitBytes: number): Promise<Array<{ context: string; allocatableMemoryBytes: number }>> {
|
|
281
|
+
let rows: Array<{ context: string; allocatableMemoryBytes: number | null }>;
|
|
282
|
+
try { rows = await this.memberMemory(); } catch { return []; }
|
|
283
|
+
return rows.flatMap((r) => r.allocatableMemoryBytes !== null && r.allocatableMemoryBytes < limitBytes
|
|
284
|
+
? [{ context: r.context, allocatableMemoryBytes: r.allocatableMemoryBytes }] : []);
|
|
285
|
+
}
|
|
286
|
+
/** Why admission is still waiting: the pool-wide lease measurement (null before any reading). Slot waits get their reason from tryAcquire's onEmpty. */
|
|
287
|
+
waitReason(): string | null {
|
|
288
|
+
const r = this.lastRaw;
|
|
289
|
+
if (!r) return null;
|
|
290
|
+
return `recommended=${r.recommended ?? 'unknown'} limitedBy=${r.limitedBy ?? '-'} memorySlots=${r.memorySlots ?? '?'}${r.reason ? ` reason=${r.reason}` : ''}`;
|
|
291
|
+
}
|
|
270
292
|
admissionSnapshot(): ReturnType<PodLeaseAdmission['snapshot']> {
|
|
271
293
|
return this.admission.snapshot();
|
|
272
294
|
}
|
|
273
295
|
/**
|
|
274
|
-
* Pick the member with the
|
|
296
|
+
* Pick the member with the largest free share: (capacity − measured cluster occupancy − this process's inflight) / capacity.
|
|
297
|
+
* POOL-SPREAD (10-06): comparing absolute free slots always picked the big member (node-b 25 vs node-c 4), so the small
|
|
298
|
+
* member idled until the big one was full. Comparing the free share fills members in proportion to their capacity.
|
|
275
299
|
* A member whose occupancy could not be read falls back to inflight-only (never treated as zero occupied).
|
|
276
300
|
* Ties keep the configured member order. Returns null when every readable member is full.
|
|
277
301
|
*/
|
|
278
|
-
async tryAcquire(): Promise<PodPoolMember | null> {
|
|
302
|
+
async tryAcquire(skip?: ReadonlySet<string>, onEmpty?: (reason: string) => void): Promise<PodPoolMember | null> {
|
|
279
303
|
let reading: Record<string, MemberOccupancy>;
|
|
280
304
|
try { reading = await this.occupancy(); }
|
|
281
305
|
catch (error) {
|
|
@@ -284,8 +308,15 @@ export class PodPoolScheduler {
|
|
|
284
308
|
}
|
|
285
309
|
const free: Record<string, number> = {};
|
|
286
310
|
const measured: Record<string, boolean> = {};
|
|
287
|
-
let best: { member: PodPoolMember; slots: number } | null = null;
|
|
311
|
+
let best: { member: PodPoolMember; slots: number; measured: boolean } | null = null;
|
|
312
|
+
// A measured member always beats an unmeasured one (its share would read 100% and draw every Job to a node
|
|
313
|
+
// that did not answer); unmeasured members compete only when no measured member has a free slot.
|
|
314
|
+
const better = (cand: { member: PodPoolMember; slots: number; measured: boolean }) => !best
|
|
315
|
+
|| (cand.measured !== best.measured ? cand.measured
|
|
316
|
+
: cand.slots / cand.member.capacity > best.slots / best.member.capacity);
|
|
288
317
|
for (const m of this.members) {
|
|
318
|
+
// LAUNCH-STALL: a member this launch cannot fit is skipped for this call only (other launches may fit it).
|
|
319
|
+
if (skip?.has(m.context)) continue;
|
|
289
320
|
const inflight = this.inflight.get(m.context) ?? 0;
|
|
290
321
|
const row = reading[m.context];
|
|
291
322
|
const ok = !!row && row.occupied !== null;
|
|
@@ -294,11 +325,16 @@ export class PodPoolScheduler {
|
|
|
294
325
|
const slots = m.capacity - occupied - inflight;
|
|
295
326
|
free[m.context] = slots;
|
|
296
327
|
if (slots <= 0) continue;
|
|
297
|
-
|
|
328
|
+
const cand = { member: m, slots, measured: ok };
|
|
329
|
+
if (better(cand)) best = cand;
|
|
330
|
+
}
|
|
331
|
+
if (!best) {
|
|
332
|
+
onEmpty?.(`no free slot · free=${JSON.stringify(free)} · measured=${JSON.stringify(measured)}${skip?.size ? ` · skipped=${[...skip].join(',')}` : ''}`);
|
|
333
|
+
return null;
|
|
298
334
|
}
|
|
299
|
-
if (!best) return null;
|
|
300
335
|
this.inflight.set(best.member.context, (this.inflight.get(best.member.context) ?? 0) + 1);
|
|
301
|
-
|
|
336
|
+
const share = Object.fromEntries(this.members.map((m) => [m.context, Math.round(Math.max(0, free[m.context] ?? 0) / m.capacity * 100) / 100]));
|
|
337
|
+
debug.log('pod.pool', 'member-selected', { member: best.member.context, free, measured, share });
|
|
302
338
|
return best.member;
|
|
303
339
|
}
|
|
304
340
|
release(member: PodPoolMember): void {
|
|
@@ -387,13 +423,14 @@ export function podImageBuildScript(cwd: string = process.cwd()): string | null
|
|
|
387
423
|
return null;
|
|
388
424
|
}
|
|
389
425
|
|
|
390
|
-
|
|
426
|
+
/** packedTgz = 풀 동기화가 한 번 만든 패키지 묶음(있으면 build.sh 가 pack 을 다시 하지 않는다). */
|
|
427
|
+
type RemoteBuild = (host: string, cluster: string, registry?: string, packedTgz?: string) => Promise<{ ok: boolean; detail: string }>;
|
|
391
428
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
429
|
+
/** 같은 트리의 패키지 묶음(tgz)을 «한 번» 만든다 — 멤버들이 같이 쓴다. cleanup 은 동기화가 끝나면 부른다. */
|
|
430
|
+
type PoolPack = () => Promise<{ ok: boolean; tgz?: string; detail: string; cleanup?: () => void }>;
|
|
431
|
+
|
|
432
|
+
function runBuildScript(script: string, env: NodeJS.ProcessEnv): Promise<{ ok: boolean; detail: string }> {
|
|
433
|
+
return new Promise((done) => {
|
|
397
434
|
const child = spawn('bash', [script], { env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
398
435
|
let out = '';
|
|
399
436
|
child.stdout.on('data', (d) => { out += d; }); child.stderr.on('data', (d) => { out += d; });
|
|
@@ -402,18 +439,53 @@ function defaultRemoteBuild(script: string, image: string): RemoteBuild {
|
|
|
402
439
|
});
|
|
403
440
|
}
|
|
404
441
|
|
|
442
|
+
/**
|
|
443
|
+
* 🩸 2026-10-06: 멤버마다 build.sh 가 같은 트리에서 `bun pm pack` 을 «동시에» 돌렸다 — prepack 이 트리에 쓰는
|
|
444
|
+
* src/version/packed-revision.json 을 다른 쪽 postpack 이 지워 한쪽이 «⛔ pack» 으로 죽었다(node-c 가 매 발사 빠졌다).
|
|
445
|
+
* ⇒ 원격 멤버가 둘 이상이면 pack 은 여기서 한 번만 하고 그 tgz 를 나눠 준다.
|
|
446
|
+
*/
|
|
447
|
+
function defaultPoolPack(script: string): PoolPack {
|
|
448
|
+
return async () => {
|
|
449
|
+
const dir = mkdtempSync(join(tmpdir(), 'elanous-pool-pack-'));
|
|
450
|
+
const tgz = join(dir, 'elanous.tgz');
|
|
451
|
+
const env: NodeJS.ProcessEnv = { ...process.env, ELANOUS_BUILD_PACK_ONLY: tgz };
|
|
452
|
+
delete env.ELANOUS_BUILD_PACKED_TGZ;
|
|
453
|
+
delete env.ELANOUS_BUILD_REMOTE;
|
|
454
|
+
const r = await runBuildScript(script, env);
|
|
455
|
+
const cleanup = () => rmSync(dir, { recursive: true, force: true });
|
|
456
|
+
if (!r.ok || !existsSync(tgz)) { cleanup(); return { ok: false, detail: r.detail }; }
|
|
457
|
+
return { ok: true, tgz, detail: r.detail, cleanup };
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function defaultRemoteBuild(script: string, image: string): RemoteBuild {
|
|
462
|
+
return (host, cluster, registry, packedTgz) => {
|
|
463
|
+
const env: NodeJS.ProcessEnv = { ...process.env, ELANOUS_BUILD_REMOTE: host, ELANOUS_L2_CLUSTER: cluster, ELANOUS_BUILD_IMAGE: image === 'elanous-harness-lite:local' ? 'lite' : 'full' };
|
|
464
|
+
delete env.ELANOUS_BUILD_REGISTRY;
|
|
465
|
+
if (registry) env.ELANOUS_BUILD_REGISTRY = registry;
|
|
466
|
+
delete env.ELANOUS_BUILD_PACK_ONLY;
|
|
467
|
+
delete env.ELANOUS_BUILD_PACKED_TGZ;
|
|
468
|
+
if (packedTgz) env.ELANOUS_BUILD_PACKED_TGZ = packedTgz;
|
|
469
|
+
return runBuildScript(script, env);
|
|
470
|
+
};
|
|
471
|
+
}
|
|
405
472
|
/**
|
|
406
473
|
* 풀의 원격 노드들을 «동시에» 이 트리의 판으로 맞춘다 (09-25 개선).
|
|
407
474
|
* 1순위 = 노드 «쪽에서» 빌드(build.sh ELANOUS_BUILD_REMOTE) — 빌드 재료(수십 MB)만 보내고 무거운 층은 그 노드의 캐시가 재사용한다.
|
|
408
475
|
* 📏 커밋이 바뀐 뒤 두 노드를 올리는 데 벽시계 55초(종전: 다시 굽기 1분 40초 ⊕ 4GB 차례 전송 5분 48초).
|
|
409
476
|
* 실패하면 = 종전의 통째 전송(syncPoolImage)으로 떨어진다.
|
|
410
477
|
*/
|
|
411
|
-
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 } = {}): Promise<Map<string, PoolImageSync>> {
|
|
478
|
+
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>> {
|
|
412
479
|
const run = deps.run ?? defaultRemoteRun;
|
|
413
480
|
const script = deps.buildScript === undefined ? podImageBuildScript() : deps.buildScript;
|
|
414
481
|
const remoteBuild = deps.remoteBuild ?? (script ? defaultRemoteBuild(script, image) : null);
|
|
415
482
|
const ship = deps.ship ?? ((m: PodPoolMember, img: string, commit: string | null, remote: RemoteRun) => syncPoolImage(m, img, commit, { run: remote, inspect: deps.inspect, transfer: deps.transfer }));
|
|
416
483
|
const results = new Map<string, PoolImageSync>();
|
|
484
|
+
// pack 한 번 ⊕ 나눠 쓰기 — 원격 멤버가 둘 이상일 때만(하나뿐이면 종전대로 build.sh 가 스스로 pack 한다).
|
|
485
|
+
// 주입된 remoteBuild(시험)에는 진짜 pack 을 붙이지 않는다 — pack 도 주입해야 쓴다.
|
|
486
|
+
const pack = deps.pack !== undefined ? deps.pack : (!deps.remoteBuild && script ? defaultPoolPack(script) : null);
|
|
487
|
+
const sharePack = pack && members.filter((m) => m.sshHost).length >= 2 ? pack : null;
|
|
488
|
+
let packed: ReturnType<PoolPack> | null = null;
|
|
417
489
|
await Promise.all(members.map(async (m) => {
|
|
418
490
|
const t0 = Date.now();
|
|
419
491
|
if (!m.sshHost) { results.set(m.context, { ok: true, action: 'local', detail: 'this machine', ms: 0 }); return; }
|
|
@@ -428,7 +500,12 @@ export async function syncPoolImages(members: readonly PodPoolMember[], image: s
|
|
|
428
500
|
return;
|
|
429
501
|
}
|
|
430
502
|
if (remoteBuild) {
|
|
431
|
-
|
|
503
|
+
// 같은 판이 아닌 멤버가 처음 여기 닿을 때 pack 한다(전부 fresh 면 pack 도 없다).
|
|
504
|
+
const shared = sharePack ? await (packed ??= sharePack()) : null;
|
|
505
|
+
if (shared && !shared.ok) debug.log('self-implement.pod', 'pool-image-pack-failed', { context: m.context, detail: shared.detail });
|
|
506
|
+
const b = shared && !shared.ok
|
|
507
|
+
? { ok: false, detail: shared.detail }
|
|
508
|
+
: await remoteBuild(m.sshHost, m.k3dCluster, m.registry, shared?.tgz);
|
|
432
509
|
const after = remoteImageCommit(m, image, run);
|
|
433
510
|
const refAfter = registryImageRef(m, localCommit ?? after, run, image);
|
|
434
511
|
// ⭐ 레지스트리 노드면 «이 판 커밋 태그가 레지스트리에 있나»로 판정한다 — Pod 는 그 커밋 태그를 pull 한다.
|
|
@@ -453,6 +530,6 @@ export async function syncPoolImages(members: readonly PodPoolMember[], image: s
|
|
|
453
530
|
return;
|
|
454
531
|
}
|
|
455
532
|
results.set(m.context, { ...ship(m, image, localCommit, run), ms: Date.now() - t0 });
|
|
456
|
-
}));
|
|
533
|
+
})).finally(async () => { if (packed) (await packed).cleanup?.(); });
|
|
457
534
|
return results;
|
|
458
535
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/** Pod Job 원천 단계 — default clone · 커밋 · PR · 호스트 번들.
|
|
2
2
|
* sha·PR·커밋은 형식 검사 전에 스크립트에 넣지 않는다(셸 주입 차단). */
|
|
3
3
|
|
|
4
|
+
import { execFileSync } from 'node:child_process';
|
|
5
|
+
|
|
4
6
|
export type PodSource =
|
|
5
|
-
| { kind: 'default' }
|
|
7
|
+
| { kind: 'default'; base?: string }
|
|
6
8
|
| { kind: 'commit'; sha: string }
|
|
7
9
|
| { kind: 'pr'; number: number }
|
|
8
10
|
| { kind: 'bundle'; bundlePath: string; sha256: string; sizeBytes: number; headCommit: string };
|
|
@@ -29,6 +31,27 @@ function assertSize(value: number): number {
|
|
|
29
31
|
return value;
|
|
30
32
|
}
|
|
31
33
|
|
|
34
|
+
/** Check the requested branch on origin before creating a Pod Job, not in the launched job. */
|
|
35
|
+
function assertRemoteBase(repoUrl: string, base: string): void {
|
|
36
|
+
if (base.startsWith('-') || base === 'HEAD') throw new Error(`invalid Pod base ref: ${base}`);
|
|
37
|
+
try {
|
|
38
|
+
execFileSync('git', ['check-ref-format', '--branch', base], { stdio: 'pipe' });
|
|
39
|
+
} catch {
|
|
40
|
+
throw new Error(`invalid Pod base ref: ${base}`);
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const advertised = execFileSync('git', ['ls-remote', '--exit-code', '--heads', repoUrl, `refs/heads/${base}`], {
|
|
44
|
+
encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
|
|
45
|
+
}).trim();
|
|
46
|
+
const [sha, ref] = advertised.split('\t');
|
|
47
|
+
if (!/^[0-9a-f]{40,64}$/.test(sha ?? '') || ref !== `refs/heads/${base}`) {
|
|
48
|
+
throw new Error('origin did not advertise the requested branch');
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
throw new Error(`Pod base ref not found on origin: ${base}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
32
55
|
/** Job 스크립트의 원천 단계. 성공 갈래는 `ELANOUS_POD_SOURCE <kind> <HEAD>` 로 끝난다. */
|
|
33
56
|
export function podSourceScript(source: PodSource, repoUrl: string, mirrorPath = '/host-mirror'): string {
|
|
34
57
|
const quotedUrl = `'${repoUrl.replace(/'/g, `'\\''`)}'`;
|
|
@@ -48,6 +71,19 @@ export function podSourceScript(source: PodSource, repoUrl: string, mirrorPath =
|
|
|
48
71
|
'cd repo || exit 5',
|
|
49
72
|
];
|
|
50
73
|
if (source.kind === 'default') {
|
|
74
|
+
if (source.base && source.base !== 'main') {
|
|
75
|
+
assertRemoteBase(repoUrl, source.base);
|
|
76
|
+
const base = source.base;
|
|
77
|
+
const quote = (value: string) => `'${value.replace(/'/g, `'\\''`)}'`;
|
|
78
|
+
const quotedTrackingRef = quote(`refs/remotes/origin/${base}`);
|
|
79
|
+
return [
|
|
80
|
+
...start,
|
|
81
|
+
`git fetch --depth 50 origin ${quote(`+refs/heads/${base}:refs/remotes/origin/${base}`)} || exit 5`,
|
|
82
|
+
`git checkout -q -B ${quote(base)} ${quotedTrackingRef} || exit 5`,
|
|
83
|
+
'head=$(git rev-parse HEAD)',
|
|
84
|
+
"printf 'ELANOUS_POD_SOURCE default %s\\n' \"$head\"",
|
|
85
|
+
].join('\n');
|
|
86
|
+
}
|
|
51
87
|
return [
|
|
52
88
|
...start,
|
|
53
89
|
'if [ "$source_via" = mirror ]; then',
|