elanous 0.2.16 → 0.2.18
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/P6YTVgzuZz5oTZhY4Din_/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1307-b46d45b705e8c9ae.js → 1307-30dff22c9e1f3073.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2537-6c289376caa88ca1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3857-4f936cdb30322aa6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4554-5761612531d0146b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4565-3d7e3fdeb43686a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5977-a11f37254196b659.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6158.9c0449a9a8d0064b.js → 6158.af2e93b7adff709b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8401-8615a660c84cdb03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9247-269e4464cc4b491c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-d92f7ee3994e84c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-3cb52e5ea8fc2fa5.js → page-4733c967b56a4277.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-d22fd849cc22f104.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-c1e20d7d338713f1.js → page-bb742f995c6769e7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-482c5dbde699947c.js → page-02b073a2ac793b83.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-60c3da708f9a0ccc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-8a9b3594bc23289b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-a0eb02638b1e6f31.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-a0e2a6d1bfb52fc5.js → page-308111abadf35670.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-6eaf7bac37830c21.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-c53598cfce6156f2.js → page-5d97b2901e0d1ea6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-13a63953f53c6a23.js → page-c39554c321576982.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-5d3c8429d04f3831.js → page-8c3d8e63ae2e1c98.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-3193bdc0bbab047a.js → page-9b72a07d424701c6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-bd5d586429167fea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-6107ca8ddd5e3bdd.js → page-a572f58cd3ca5864.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-59b7834d497c2255.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-be5ea965a07fbc41.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-c130b63862437814.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-fa244bd146a00ae8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-44d56d6391965a6a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-4167348049166852.js → page-83a700d4c754d802.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-de52e8fdf9095f7a.js → not-found-7b3170f374f77b9d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-5f23d0c66a3b4793.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-ca3952cd78a83ca2.js → page-a3c58f5c72a5f3f9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a2c4625596f8d69c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-0e9099531d239f11.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-e8e1ecaea7cd71d2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-a1b7ffed710a93e0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-1ebfe049c640e171.js → page-1467047104141297.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-2da6f9b8a38c6978.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-102310930791197a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-f331e38fefc00393.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-6ed0b73bae8351d8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-abe8e3b74bea1ffc.js → page-44d5f767491692bf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-43336055451f6e37.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/{page-d28573d6792b4275.js → page-3cc47dca0922ab9a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-a77ee355a2304ff0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-d751de5f8062ff5e.js → page-b17cd46cf0e9b8cf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/{page-fe1759f94003b32b.js → page-61553851aa606146.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/{page-d64c984e855c8c68.js → page-a1f585e154e61c59.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-7bf54c9bb9274af8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/{page-9afdee66b820c76e.js → page-5150ef17b5383a57.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b9d8ce70792c1672.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-a5a3de231f2cc7d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-3544f154c87b43ee.js → page-134da468538baf48.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31ac4afa2dcd915c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{webpack-539cde99554be20f.js → webpack-c2782b330447ffb6.js} +1 -1
- package/apps/pwa/out/_next/static/css/7fa358fec7da7c47.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +14 -14
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +14 -14
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +14 -14
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +14 -14
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +14 -14
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +14 -14
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +14 -14
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +14 -14
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +14 -14
- package/apps/pwa/out/decisions/index.html +1 -0
- package/apps/pwa/out/decisions/index.txt +30 -0
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +14 -14
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +14 -14
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +14 -14
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +14 -14
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +14 -14
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +14 -14
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +14 -14
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +14 -14
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +14 -14
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +14 -14
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +14 -14
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +14 -14
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +14 -14
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +14 -14
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +14 -14
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +14 -14
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +15 -15
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +14 -14
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +14 -14
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +14 -14
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +14 -14
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +14 -14
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +14 -14
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +14 -14
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +14 -14
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +14 -14
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +14 -14
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +14 -14
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +14 -14
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +14 -14
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +14 -14
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +14 -14
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +14 -14
- package/graphs/intake/intake-daily.yaml +9 -2
- package/graphs/intake/news-sources.yaml +4 -0
- package/graphs/intake/recipes.yaml +6 -1
- package/package.json +2 -1
- package/samples/workflows/templates/approval-gated-deploy.yaml +30 -0
- package/samples/workflows/templates/chat-driven-research.yaml +35 -0
- package/samples/workflows/templates/discord-listen-and-respond.yaml +29 -0
- package/samples/workflows/templates/http-poll-then-alert.yaml +30 -0
- package/samples/workflows/templates/manual-quickstart.yaml +21 -0
- package/samples/workflows/templates/scheduled-daily-digest.yaml +30 -0
- package/samples/workflows/templates/webhook-deploy-hook.yaml +27 -0
- package/scripts/__fixtures__/intake-news.xml +7 -0
- package/scripts/ci-public-export-import-gate.ts +7 -2
- package/scripts/ci-typecheck-changed.ts +21 -6
- package/scripts/discord-seat-probe.ts +3 -2
- package/scripts/docs/generate-cli-anatomy.ts +162 -0
- package/scripts/goals/authoring-metrics.ts +73 -0
- package/scripts/intake-news.ts +169 -0
- package/scripts/landing-heal/must-fix-match.ts +7 -7
- package/scripts/landing-heal/run-mission.ts +3 -2
- package/scripts/public-export-test-run.ts +37 -2
- package/scripts/release-loop/checklist-gate-node.ts +13 -11
- package/scripts/release-loop/cut-branch.ts +2 -1
- package/scripts/release-loop/cutoff.ts +4 -2
- package/scripts/release-loop/docs-land-node.ts +77 -11
- package/scripts/release-loop/docs-node.ts +1 -1
- package/scripts/release-loop/gate-node.ts +99 -14
- package/scripts/release-loop/node-verdict.ts +2 -1
- package/scripts/release-loop/npm-publish-node.ts +11 -7
- package/scripts/release-loop/ops-upgrade-node.ts +8 -1
- package/scripts/release-loop/preflight.ts +246 -0
- package/scripts/release-loop/publish-node.ts +10 -3
- package/scripts/release-loop/release-docs.ts +134 -0
- package/scripts/release-loop/release-readiness.ts +11 -6
- package/scripts/release-loop/release-version.ts +48 -0
- package/scripts/release-loop/resume-release.ts +115 -0
- package/scripts/release-loop/unattended-release.ts +43 -7
- package/scripts/release-loop/version-node.ts +149 -41
- package/scripts/release-story/draft.ts +42 -14
- package/scripts/rhythm/daily.ts +25 -4
- package/scripts/skills/public-check.ts +62 -12
- package/scripts/test-deterministic.ts +51 -33
- package/scripts/test-diet/effectiveness.ts +62 -1
- package/scripts/test-diet/lib.ts +1 -1
- package/scripts/test-diet/node.ts +8 -1
- package/src/agent-mission/driver.ts +53 -10
- package/src/agent-mission/review-loop.ts +4 -3
- package/src/agent-substrate/pr-comment-meta.ts +20 -0
- package/src/autopilot/build/llm-conflict-merge.ts +28 -8
- package/src/autopilot/track-agent.ts +5 -3
- package/src/briefing/brief-items.ts +86 -17
- package/src/cli/autopilot-cli.ts +86 -1
- package/src/cli/brief-cli.ts +36 -2
- package/src/cli/decisions-cli.ts +4 -1
- package/src/cli/freeze-cli.ts +3 -2
- package/src/cli/intake-cli.ts +74 -2
- package/src/cli/log-event-names.ts +1 -0
- package/src/cli/notify-cli.ts +75 -0
- package/src/cli/pr-cli.ts +12 -1
- package/src/cli/provider-cli.ts +24 -0
- package/src/cli/release-cli.ts +101 -17
- package/src/cli/self-update-canary.ts +46 -0
- package/src/cli/self-update.ts +200 -1
- package/src/cli/tasks-cli.ts +309 -3
- package/src/context-bus/condense.ts +19 -3
- package/src/context-bus/context-day.ts +77 -24
- package/src/context-bus/context-first.ts +33 -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 +20 -2
- package/src/discord-slash-wire.ts +1 -0
- package/src/domains/outbound-alert.ts +200 -27
- package/src/domains/outbound-log-probe.ts +16 -1
- package/src/domains/release-tool.ts +2 -2
- package/src/execution-loop/gates.ts +11 -2
- package/src/flow-loop/split.ts +8 -4
- package/src/git-fs/worktree.ts +23 -3
- package/src/harness/harness-cli-command.ts +151 -11
- package/src/harness/harness-pod-dispatch.ts +11 -0
- package/src/harness/harness-queue.ts +466 -32
- package/src/harness/helper-scan.ts +10 -3
- package/src/harness/nested-elanous-policy.ts +23 -4
- package/src/hq/fence-wrapper.ts +123 -0
- package/src/hq/hq.ts +8 -0
- package/src/hq/seats.ts +369 -0
- package/src/index.ts +190 -23
- package/src/intake-plane/adopt-gate.ts +49 -0
- package/src/intake-plane/check.ts +3 -0
- package/src/intake-plane/collect-github.ts +57 -25
- package/src/intake-plane/digest.ts +39 -4
- package/src/intake-plane/discord-seat-work.ts +8 -2
- package/src/intake-plane/requirement-funnel.ts +316 -0
- package/src/knowledge/know-find.ts +10 -1
- package/src/loops/budget.ts +36 -0
- package/src/loops/orchestrator/finish-rate.ts +73 -1
- package/src/loops/orchestrator/grid-place.ts +11 -0
- package/src/loops/orchestrator/tick.ts +295 -20
- package/src/maturity/feature-maturity.ts +3 -2
- package/src/nexus/api/decisions-route.ts +11 -1
- package/src/nexus/api/grid.ts +61 -0
- package/src/nexus/api/http-server.ts +19 -1
- package/src/nexus/api/loop-edges.ts +69 -13
- package/src/nexus/api/ops-seats-sources.ts +2 -2
- package/src/nexus/api/task-cards-api.ts +19 -0
- package/src/nexus/api/workflow-templates.ts +3 -0
- package/src/oauth/codex.ts +120 -2
- package/src/onboarding/first-chat-gate.ts +29 -0
- package/src/release-loop/checklist.ts +104 -6
- package/src/release-loop/feature-store.ts +10 -9
- package/src/release-loop/landing-freeze.ts +24 -14
- package/src/release-loop/placement.ts +13 -3
- package/src/release-loop/rubric.ts +80 -0
- package/src/seat-dispatch/seat-ask.ts +79 -16
- package/src/seat-loop/seat-loop.ts +107 -37
- package/src/self-dev/ask-launch-flow.ts +50 -10
- package/src/self-dev/ask-path-candidates.ts +68 -0
- package/src/self-dev/dev-pipeline.ts +4 -0
- package/src/self-dev/draft-triage-rules.ts +1 -1
- package/src/self-dev/orchestrate-cli.ts +22 -0
- package/src/self-dev/round-pr-supersede.ts +244 -0
- package/src/self-dev/run-store.ts +5 -1
- package/src/self-dev/run-supervisor.ts +21 -1
- package/src/self-dev/supervisor-verdict-edges.ts +46 -0
- package/src/self-implement/follow-up-goals.ts +2 -2
- package/src/self-implement/frozen-merges.ts +25 -20
- package/src/self-implement/gate-remote.ts +427 -0
- 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 +93 -12
- 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/host-regate.ts +3 -1
- package/src/self-implement/l8-merge-queue.ts +29 -5
- package/src/self-implement/orchestrator.ts +78 -13
- package/src/self-implement/prior-draft-findings.ts +4 -3
- package/src/skills/skill-feedback.ts +76 -0
- package/src/task-agent/actions.ts +151 -0
- package/src/task-agent/judge.ts +122 -0
- package/src/task-agent/mission.ts +423 -0
- package/src/task-agent/shadow.ts +90 -0
- package/src/task-agent/task-hand.ts +235 -0
- package/src/task-cards/card-store.ts +4 -1
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +43 -4
- package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
- package/src/task-orchestrator/surfaces/pod-pool.ts +81 -13
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +225 -35
- package/src/telegram-commands.ts +1 -1
- package/src/telegram-loops-command.ts +22 -15
- package/src/telegram.ts +5 -20
- package/src/user-config.ts +86 -4
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4614-21403aac730d4586.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6877-7c6208705c284124.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8949-ecb4128a0858b911.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/approvals/page-79c3ed84baaedd03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-a424ef80885d1743.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-1e962b31d1f11081.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-8c2d9526164f5d09.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-ffff6d819fbc6e0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-76270efaee638f17.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-dce686e582c23937.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-d3d53b146416e422.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-1a61c7ca1ceb3ae7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-51f811fa119308b6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-dacff46049e1557e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-36a7d994f4ca0299.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-5b042736aa113756.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-acb749f1e432bcf3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-cd78862707999911.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-54b9ef8afe6278fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-57924393e3e4e539.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-9e0ce6c8129fa641.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-31fa1ce4c4aff9ae.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-0f5cd57ca928f42b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-4364ef9f4e7c7f0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-59f03fda76c03d22.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-86914b0dee8ab7f8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d4aef67abf264874.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-1ea64acadd704ace.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-30c2073bfc21c004.js +0 -1
- package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +0 -1
- package/apps/pwa/out/_next/static/rv9cierV0jSOvDBm1g0XB/_buildManifest.js +0 -1
- /package/apps/pwa/out/_next/static/{rv9cierV0jSOvDBm1g0XB → P6YTVgzuZz5oTZhY4Din_}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import { inQuietHours, sendOutbound, userRecentlyActive } from '../domains/outbound-alert.js';
|
|
3
|
+
import { isTradingKind } from '../domains/telegram-kind-route.js';
|
|
4
|
+
|
|
5
|
+
export type NotifyPlan = { ok: true; kind: string } | { ok: false; exitCode: 2; message: string };
|
|
6
|
+
|
|
7
|
+
export function planNotify({ kind = 'op-report', text }: { kind?: string; text: string }): NotifyPlan {
|
|
8
|
+
if (isTradingKind(kind)) {
|
|
9
|
+
return { ok: false, exitCode: 2, message: `매매 kind(${kind})는 운영 보고에 쓸 수 없습니다 — op-report 또는 ops-alert 를 쓰세요` };
|
|
10
|
+
}
|
|
11
|
+
if (!kind.trim() || /[\r\n]/.test(kind)) {
|
|
12
|
+
return { ok: false, exitCode: 2, message: 'kind 가 비었거나 줄바꿈을 포함합니다 — op-report 또는 ops-alert 를 쓰세요' };
|
|
13
|
+
}
|
|
14
|
+
if (!text.trim()) return { ok: false, exitCode: 2, message: '본문이 비었습니다 — elanous notify <text...> 또는 --stdin' };
|
|
15
|
+
return { ok: true, kind };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface NotifyCliDeps {
|
|
19
|
+
send?: (text: string, kind: string) => boolean | Promise<boolean>;
|
|
20
|
+
quiet?: () => boolean;
|
|
21
|
+
readStdin?: () => Promise<string>;
|
|
22
|
+
output?: (line: string) => void;
|
|
23
|
+
error?: (line: string) => void;
|
|
24
|
+
setExitCode?: (code: number) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function registerNotifyCommand(program: Command, deps: NotifyCliDeps = {}): void {
|
|
28
|
+
const send = deps.send ?? sendOutbound;
|
|
29
|
+
const quiet = deps.quiet ?? (() => inQuietHours() && !userRecentlyActive());
|
|
30
|
+
const readStdin = deps.readStdin ?? (async () => {
|
|
31
|
+
if (process.stdin.isTTY) throw new Error('--stdin requires redirected input');
|
|
32
|
+
const chunks: Buffer[] = [];
|
|
33
|
+
for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
|
|
34
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
35
|
+
});
|
|
36
|
+
const output = deps.output ?? ((line: string) => console.log(line));
|
|
37
|
+
const error = deps.error ?? ((line: string) => console.error(line));
|
|
38
|
+
const setExitCode = deps.setExitCode ?? ((code: number) => { process.exitCode = code; });
|
|
39
|
+
|
|
40
|
+
program.command('notify [text...]')
|
|
41
|
+
.description('대표에게 운영 보고를 발송한다 (기본 kind: op-report)')
|
|
42
|
+
.option('--kind <kind>', '운영 알림 kind (매매 report|alert|digest 거부)')
|
|
43
|
+
.option('--stdin', '본문을 표준 입력에서 읽는다')
|
|
44
|
+
.option('--json', '결과를 JSON 한 줄로 출력한다')
|
|
45
|
+
.action(async (parts: string[], opts: { kind?: string; stdin?: boolean; json?: boolean }) => {
|
|
46
|
+
const kind = opts.kind ?? 'op-report';
|
|
47
|
+
if (opts.stdin && parts.length) {
|
|
48
|
+
if (opts.json) output(JSON.stringify({ ok: false, kind, chars: 0, deferred: false }));
|
|
49
|
+
else error('--stdin 과 본문 인자는 함께 쓸 수 없습니다');
|
|
50
|
+
setExitCode(2);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const text = opts.stdin ? await readStdin() : parts.join(' ');
|
|
55
|
+
const plan = planNotify({ kind, text });
|
|
56
|
+
if (!plan.ok) {
|
|
57
|
+
if (opts.json) output(JSON.stringify({ ok: false, kind, chars: [...text].length, deferred: false }));
|
|
58
|
+
else error(plan.message);
|
|
59
|
+
setExitCode(plan.exitCode);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const deferred = quiet();
|
|
63
|
+
const ok = await send(text, plan.kind);
|
|
64
|
+
const chars = [...text].length;
|
|
65
|
+
if (opts.json) output(JSON.stringify({ ok, kind: plan.kind, chars, deferred: ok && deferred }));
|
|
66
|
+
else if (ok) output(`${deferred ? '보류됨' : '보냄'}(${plan.kind}) · ${chars}자`);
|
|
67
|
+
else error(`못 보냄(${plan.kind}) — elanous logs --category outbound.send 로 확인`);
|
|
68
|
+
setExitCode(ok ? 0 : 1);
|
|
69
|
+
} catch {
|
|
70
|
+
if (opts.json) output(JSON.stringify({ ok: false, kind, chars: 0, deferred: false }));
|
|
71
|
+
else error(`못 보냄(${kind}) — elanous logs --category outbound.send 로 확인`);
|
|
72
|
+
setExitCode(1);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
package/src/cli/pr-cli.ts
CHANGED
|
@@ -75,6 +75,8 @@ export interface PrLandOpts {
|
|
|
75
75
|
excludeActiveRunFiles?: boolean;
|
|
76
76
|
excludeActiveRunSources?: boolean;
|
|
77
77
|
landReason?: string;
|
|
78
|
+
/** Explicit reason for bypassing the landing freeze. */
|
|
79
|
+
forceFreeze?: string;
|
|
78
80
|
/** Land even though changed files would leak into the public export (the reason goes in --land-reason). */
|
|
79
81
|
allowPublicLeak?: boolean;
|
|
80
82
|
}
|
|
@@ -130,6 +132,8 @@ export interface PrLandDeps {
|
|
|
130
132
|
overlapConfirmChannels?: ConfirmChannel[];
|
|
131
133
|
overlapConfirmTimeoutMs?: number;
|
|
132
134
|
decideOverlapLanding?: () => OverlapLandingDecision | Promise<OverlapLandingDecision>;
|
|
135
|
+
/** Operational freeze authority (isolated in tests). */
|
|
136
|
+
prodFreezeRoot?: string;
|
|
133
137
|
}
|
|
134
138
|
|
|
135
139
|
export const OVERLAP_DECISION_PROMPT = '겹친 파일이 있습니다. 이번 착지를 그대로 병합할까요?';
|
|
@@ -1132,6 +1136,10 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
|
|
|
1132
1136
|
out.error('✗ body: --body와 --body-file은 함께 사용할 수 없습니다.');
|
|
1133
1137
|
return 1;
|
|
1134
1138
|
}
|
|
1139
|
+
if (opts.forceFreeze !== undefined && !opts.forceFreeze.trim()) {
|
|
1140
|
+
out.error('✗ freeze: --force-freeze 에는 이유가 필요합니다.');
|
|
1141
|
+
return 1;
|
|
1142
|
+
}
|
|
1135
1143
|
if (opts.allowPublicLeak && !opts.landReason?.trim()) {
|
|
1136
1144
|
out.error('✗ public-export-leak: --allow-public-leak 에는 --land-reason "<이유>" 가 필요합니다.');
|
|
1137
1145
|
return 1;
|
|
@@ -1649,7 +1657,8 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
|
|
|
1649
1657
|
const headCommit = landedHead.ok && /^[a-f0-9]{40}$/i.test(landedHead.out.trim()) ? landedHead.out.trim() : undefined;
|
|
1650
1658
|
const validPr = Number.isSafeInteger(prNumber) && prNumber > 0;
|
|
1651
1659
|
const heldEntry = validPr && headCommit ? { prNumber, headCommit, repoRoot: cwd } : null;
|
|
1652
|
-
const admission = admitLandingMerge(heldEntry, undefined, {}, validPr ? { prNumber, repoRoot: cwd, ...(headCommit ? { headCommit } : {}) } : undefined
|
|
1660
|
+
const admission = admitLandingMerge(heldEntry, undefined, {}, validPr ? { prNumber, repoRoot: cwd, ...(headCommit ? { headCommit } : {}) } : undefined,
|
|
1661
|
+
{ prodFreezeRoot: deps.prodFreezeRoot, forceReason: opts.forceFreeze });
|
|
1653
1662
|
if (admission.kind === 'held' && !heldEntry) {
|
|
1654
1663
|
out.error(`✗ freeze: ready PR의 검증 HEAD/번호를 보존하지 못했습니다: ${upsert.url}`);
|
|
1655
1664
|
return 1;
|
|
@@ -1712,6 +1721,7 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
|
|
|
1712
1721
|
}
|
|
1713
1722
|
return 1;
|
|
1714
1723
|
}
|
|
1724
|
+
if (opts.forceFreeze) debug.log('release-loop.freeze', 'force-merge', { pr: prNumber, reason: opts.forceFreeze.trim() });
|
|
1715
1725
|
record('merge', true, { result: merged.kind, ...(merged.remoteBranchDeletion ? { remoteBranchDeletion: merged.remoteBranchDeletion.detail } : {}) });
|
|
1716
1726
|
if (merged.remoteBranchDeletion) {
|
|
1717
1727
|
out.log(`⚠ remote branch deletion: ${merged.remoteBranchDeletion.detail}`);
|
|
@@ -1763,6 +1773,7 @@ export function registerPrCommands(program: Command, deps: PrLandDeps = {}, queu
|
|
|
1763
1773
|
.option('--exclude-active-run-files', '도는 런과 겹친 파일을 이번 착지에서 제외')
|
|
1764
1774
|
.option('--exclude-active-run-sources', '⚠ 위험: --exclude-active-run-files 가 골 문서가 아닌 파일(소스)을 빼도 착지를 강행')
|
|
1765
1775
|
.option('--land-reason <text>', '권고를 보고도 지금 내는 이유(예: 남이 기다리는 차단 해제)')
|
|
1776
|
+
.option('--force-freeze <reason>', '동결을 넘겨 병합할 명시적 이유(필수)')
|
|
1766
1777
|
.option('--allow-public-leak', '바뀐 파일이 공개본에 사적 흔적을 실어도 착지(이유는 --land-reason)')
|
|
1767
1778
|
.action(async (opts: PrLandOpts) => {
|
|
1768
1779
|
// ⛔⭐⭐⭐ **관측 sink 를 먼저 건다** — 라이브 도그푸드가 잡은 결함(2026-08-03).
|
package/src/cli/provider-cli.ts
CHANGED
|
@@ -392,6 +392,30 @@ export function registerProviderCommands(program: Command): void {
|
|
|
392
392
|
console.log(' 확인: bun bin/elanous.mjs provider codex account list · bun bin/elanous.mjs usage');
|
|
393
393
|
});
|
|
394
394
|
|
|
395
|
+
// POD-TOKEN-PREREFRESH (10-06): 사람이 두 번 손으로 한 «백업 → refresh → 원자적 0600 쓰기 → import» 를 한 명령으로.
|
|
396
|
+
// ⛔ 토큰 값은 안 찍는다 — 남은 시간 전→후만. ⛔ default(~/.codex · 대표 기본 계정)는 거부.
|
|
397
|
+
accountCmd
|
|
398
|
+
.command('refresh <name>')
|
|
399
|
+
.description('그 계정의 codex 토큰을 호스트에서 갱신해 정본 스토어로 반영한다 (⛔ default 거부 · 토큰 값은 안 찍는다 · ⚠️ refresh 토큰은 갱신마다 회전 — 본부 한 곳에서만)')
|
|
400
|
+
.action(async (name: string) => {
|
|
401
|
+
const { refreshCodexAccountHome, codexAccountRefreshLockPath, withCodexAccountRefreshLock, CodexRefreshLockUnavailableError } = await import('../oauth/codex.js');
|
|
402
|
+
// PREREFRESH-LOCK: the same per-account lock as the Pod pre-refresh — a hand refresh never races a launch.
|
|
403
|
+
// 이름 있는 계정의 잠금 자리를 못 구하면 잠금 없이 갱신하지 않는다(Pod 선갱신과 경합하는 자리) — default 는 refreshCodexAccountHome 이 거부한다.
|
|
404
|
+
const lockPath = name === 'default' ? null : codexAccountRefreshLockPath(name);
|
|
405
|
+
if (name !== 'default' && !lockPath) { console.error(`갱신 거부: ${name} 의 잠금 자리를 못 구했다(계정 홈 없음) — 잠금 없이 갱신하지 않는다`); process.exitCode = 1; return; }
|
|
406
|
+
let r: Awaited<ReturnType<typeof refreshCodexAccountHome>>;
|
|
407
|
+
try { r = await withCodexAccountRefreshLock(lockPath, () => refreshCodexAccountHome(name)); }
|
|
408
|
+
catch (lockError) {
|
|
409
|
+
if (!(lockError instanceof CodexRefreshLockUnavailableError)) throw lockError;
|
|
410
|
+
const why = lockError.reason === 'lock-timeout' ? '다른 갱신이 진행 중 — 잠시 뒤 다시' : `잠금 파일을 못 만들었다: ${lockError.message}`;
|
|
411
|
+
console.error(`갱신 거부: ${name} 의 갱신 잠금을 못 잡았다(${why})`); process.exitCode = 1; return;
|
|
412
|
+
}
|
|
413
|
+
if (!r.ok) { console.error(`갱신 실패(${r.kind}): ${r.message}`); process.exitCode = 1; return; }
|
|
414
|
+
const h = (v: number | null) => (v === null ? '?' : `${v}h`);
|
|
415
|
+
console.log(`✅ ${name} 갱신 — 남은 시간 ${h(r.beforeH)} → ${h(r.afterH)} · storeKey=${r.storeKey}`);
|
|
416
|
+
console.log(` 백업: ${r.backupPath}`);
|
|
417
|
+
});
|
|
418
|
+
|
|
395
419
|
const resetCreditsCmd = codexCmd.command('reset-credits').description('리셋 크레딧 — 조회 · 관측 · 사용(⛔ 사용은 되돌릴 수 없다)');
|
|
396
420
|
|
|
397
421
|
resetCreditsCmd
|
package/src/cli/release-cli.ts
CHANGED
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
// elanous release tag --version <x.y.z> --source <commit> [--yes]
|
|
6
6
|
// elanous release verify [--version <x.y.z>]
|
|
7
7
|
// elanous release notes --from <ref> [--to <ref>]
|
|
8
|
-
// elanous release run --version <x.y.z> [--cut-commit <sha>] [--dry-run] [--json] [--if-ready]
|
|
8
|
+
// elanous release run --version <x.y.z> [--prerelease rc] [--main-cut | --cut-commit <sha>] [--dry-run] [--json] [--if-ready]
|
|
9
|
+
// elanous release resume --run <runId> --from <node> [--json] (release/<v> 수리 뒤 새 끝으로 이어 달리기)
|
|
9
10
|
// elanous release auto-start [--window-minutes <n>] [--apply] [--json]
|
|
11
|
+
// elanous release preflight --version <x.y.z> [--publish-at <iso>] [--json] (컷 30분 전 사전 점검 · 읽기 전용 · ⛔ 있으면 exit 1)
|
|
10
12
|
// elanous release cut-branch --version <x.y.z> --base <sha> --pick <sha>... [--dry-run]
|
|
11
13
|
//
|
|
12
14
|
// 🩸 계기(2026-09-25 v0.1.0 첫 공개): 손으로 밟은 절차에서 둘을 빠뜨릴 뻔했다 — 공개본 git 커밋(판 커밋이 공개본 커밋이 된다) ·
|
|
@@ -25,11 +27,12 @@ import { effectiveInstanceRoot, releaseLedgerRoot } from '../instance/resolve.js
|
|
|
25
27
|
import { getElanousConfigDirOverride } from '../elanous-config-dir.js';
|
|
26
28
|
import { envLiteral } from '../platform/env-literal.js';
|
|
27
29
|
import { getUserConfig, userConfigPath } from '../user-config.js';
|
|
28
|
-
import { addItem, claimItem, decodeClaimHistoryEntry, devVersion, listChecklist, ownerMatches, parseOwner, parityGap, removeItem, seedFromRoadmap, setItem, summarizeChecklist, type ChecklistStatus, type ChecklistDisposition, type ChecklistKind } from '../release-loop/checklist.js';
|
|
30
|
+
import { addItem, cellsReferencingDoc, claimItem, decodeClaimHistoryEntry, devVersion, listChecklist, ownerMatches, parseOwner, parityGap, removeItem, renderRefsStatus, seedFromRoadmap, setItem, summarizeChecklist, type ChecklistStatus, type ChecklistDisposition, type ChecklistKind } from '../release-loop/checklist.js';
|
|
29
31
|
import * as features from '../release-loop/feature-store.js';
|
|
30
32
|
import { evidencePlan, landedButYellow, type MergedChecklistPr } from '../release-loop/landed-but-yellow.js';
|
|
31
33
|
import { formatSchedule, getSchedule, listSchedules, setSchedule } from '../release-loop/release-schedule.js';
|
|
32
34
|
import { placeCell, rebalance, seatMove, type PlacementPriority } from '../release-loop/placement.js';
|
|
35
|
+
import { parseRubric, readRubricItems, rubricGrade, rubricScore } from '../release-loop/rubric.js';
|
|
33
36
|
import { writeStdoutJson } from './stdout-json.js';
|
|
34
37
|
import { CliUserError } from './cli-user-error.js';
|
|
35
38
|
import { hqCliWriteAllowed, type HqDeps } from '../hq/hq.js';
|
|
@@ -37,8 +40,11 @@ import { fileLeaseStore } from '../hq/lease.js';
|
|
|
37
40
|
import { isIsolatedLedgerWriteRoot } from '../hq/ledger-write-target.js';
|
|
38
41
|
import { runUnattendedRelease, type UnattendedReleaseDeps } from '../../scripts/release-loop/unattended-release.js';
|
|
39
42
|
import { cutReleaseBranch } from '../../scripts/release-loop/cut-branch.js';
|
|
43
|
+
import { resumeReleaseRun } from '../../scripts/release-loop/resume-release.js';
|
|
44
|
+
import type { PrereleaseKind } from '../../scripts/release-loop/release-version.js';
|
|
40
45
|
import { releaseReadiness } from '../../scripts/release-loop/release-readiness.js';
|
|
41
46
|
import { runReleaseIfReady } from './release-run-if-ready.js';
|
|
47
|
+
import { formatPreflight, releasePreflight } from '../../scripts/release-loop/preflight.js';
|
|
42
48
|
import { autoStartScheduledRelease, type AutoStartDeferral } from '../release-loop/auto-start.js';
|
|
43
49
|
import { landingFreezeMessage, LandingFrozenError, readLandingFreeze } from '../release-loop/landing-freeze.js';
|
|
44
50
|
|
|
@@ -151,7 +157,7 @@ export async function prepareRelease(opts: PrepareOptions, run: Runner = default
|
|
|
151
157
|
must(run('git', ['add', '-A'], publicDir), 'git add');
|
|
152
158
|
must(run('git', ['-c', `user.name=${name}`, '-c', `user.email=${email}`, 'commit', '-q', '--allow-empty', '-m', `elanous ${tag}`], publicDir), 'git commit');
|
|
153
159
|
const publicCommit = must(run('git', ['rev-parse', 'HEAD'], publicDir), 'git rev-parse');
|
|
154
|
-
log(`③ 공개 커밋 ${publicCommit.slice(0, 12)} (${publicRepo} main
|
|
160
|
+
log(`③ 공개 커밋 ${publicCommit.slice(0, 12)} (${publicRepo} main 위${isPrerelease(opts.version) ? ` · 미리보기 — 공개 main 이 아니라 release/${opts.version} 가지 ⊕ 태그로 나간다` : ''})`);
|
|
155
161
|
|
|
156
162
|
// ④ PWA 빌드 — ⛔ 빠뜨리면 «웹 화면 없는 판»(빌드 산출은 공개본에 안 실린다).
|
|
157
163
|
symlinkSync(join(modulesRoot, 'node_modules'), join(publicDir, 'node_modules'), 'dir');
|
|
@@ -216,12 +222,17 @@ export function planPublish(m: ReleaseManifest, notesFile: string): PublishStep[
|
|
|
216
222
|
// 🩸 2026-09-25 v0.1.1 드라이런: release-build 의 `files` 에는 SHA256SUMS 가 없어(체크섬 대상 목록이라) «자산 4» 로 올릴 뻔했다.
|
|
217
223
|
const assets = [...new Set([...m.files.map((f) => f.name), 'SHA256SUMS'])];
|
|
218
224
|
return [
|
|
219
|
-
|
|
225
|
+
// RELEASE-REHEARSAL-RC: a prerelease never moves public main — its export goes to release/<v> plus its tag (one atomic push).
|
|
226
|
+
m.prerelease
|
|
227
|
+
? { what: `공개 저장소 ${m.publicRepo} release/${m.version} 가지 ⊕ 태그 ${m.tag} 푸시(${m.publicCommit.slice(0, 12)} · main 무접촉)`, command: 'git',
|
|
228
|
+
args: ['push', '--atomic', 'origin', `${m.publicCommit}:refs/heads/release/${m.version}`, `${m.publicCommit}:refs/tags/${m.tag}`], cwd: m.publicDir }
|
|
229
|
+
: { what: `공개 저장소 ${m.publicRepo} main 푸시(${m.publicCommit.slice(0, 12)})`, command: 'git', args: ['push', 'origin', 'HEAD:main'], cwd: m.publicDir },
|
|
220
230
|
{
|
|
221
231
|
what: `릴리스 ${m.tag}${m.prerelease ? ' (prerelease)' : ''} 생성 ⊕ 자산 ${assets.length}(${assets.join(' · ')})`,
|
|
222
232
|
command: 'gh',
|
|
223
233
|
args: ['release', 'create', m.tag, ...assets.map((name) => join(m.distDir, name)), '--repo', m.publicRepo, '--target', m.publicCommit,
|
|
224
|
-
|
|
234
|
+
// RELEASE-REHEARSAL-RC: a prerelease is never Latest — the one-line installer keeps serving the last stable release.
|
|
235
|
+
'--title', `elanous ${m.tag}${m.prerelease ? ' (pre-release)' : ''}`, '--notes-file', notesFile, ...(m.prerelease ? ['--prerelease', '--latest=false', '--verify-tag'] : [])],
|
|
225
236
|
cwd: m.publicDir,
|
|
226
237
|
},
|
|
227
238
|
];
|
|
@@ -660,6 +671,36 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
660
671
|
};
|
|
661
672
|
const withContext = (cmd: Command) => cmd.option('--version <v>', '판 또는 별칭').option('--json', '결과 JSON').option('--hq-override', '본부 임대 거부를 관측하며 수동 우회');
|
|
662
673
|
const actor = () => process.env.ELANOUS_TRACK || 'cli';
|
|
674
|
+
withContext(checklist.command('rubric').description('루브릭 채점(읽기 전용)'))
|
|
675
|
+
.action(async (_opts: unknown, cmd: Command) => {
|
|
676
|
+
if (cmd.opts().version === undefined && cmd.parent?.opts().version === undefined) throw new CliUserError('조회할 판을 지정하라', '--version <v>');
|
|
677
|
+
const { version, json } = context(cmd);
|
|
678
|
+
const items = readRubricItems(version, releaseLedgerRoot());
|
|
679
|
+
const rows = items.flatMap((item) => {
|
|
680
|
+
const rubric = parseRubric(`${item.title}\n${item.evidence ?? ''}`);
|
|
681
|
+
if (!rubric) return [];
|
|
682
|
+
const score = rubricScore(rubric);
|
|
683
|
+
return [{ id: item.id, score, grade: rubricGrade(score), priority: item.priority ?? null }];
|
|
684
|
+
}).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
|
|
685
|
+
const missing = items.length - rows.length;
|
|
686
|
+
if (json) { await writeStdoutJson(`${JSON.stringify({ version, rows, missing })}\n`); return; }
|
|
687
|
+
console.log('id · 점수 · 등급 · 현재 우선순위');
|
|
688
|
+
for (const row of rows) console.log(`${row.id} · ${row.score} · ${row.grade} · ${row.priority ?? '-'}`);
|
|
689
|
+
console.log(`루브릭 없는 칸 ${missing}`);
|
|
690
|
+
});
|
|
691
|
+
withContext(checklist.command('refs <doc>').description('문서를 인용하는 칸 — 모든 판의 구현 현황(읽기 전용)'))
|
|
692
|
+
.action(async (doc: string, _opts: unknown, cmd: Command) => {
|
|
693
|
+
if (!doc.trim() || doc.trim().startsWith('#')) throw new CliUserError(`잘못된 문서: ${doc}`, '저장소 상대 경로[#절] — 예: docs/RFC-x.md#§3');
|
|
694
|
+
const { json } = context(cmd);
|
|
695
|
+
const ledgerRoot = releaseLedgerRoot();
|
|
696
|
+
const rows = cellsReferencingDoc(doc, features.assignedVersions(ledgerRoot).map((version) => listChecklist(version, ledgerRoot)));
|
|
697
|
+
const byStatus = { green: 0, yellow: 0, red: 0, done: 0 } as Record<ChecklistStatus, number>;
|
|
698
|
+
for (const row of rows) byStatus[row.status] += 1;
|
|
699
|
+
debug.log('release.checklist', 'refs-read', { doc, rows: rows.length });
|
|
700
|
+
if (json) { await writeStdoutJson(`${JSON.stringify({ doc, rows, byStatus })}\n`); return; }
|
|
701
|
+
console.log(renderRefsStatus(rows));
|
|
702
|
+
console.log(`칸 ${rows.length} · 🟢 ${byStatus.green} · 🟡 ${byStatus.yellow} · 🔴 ${byStatus.red} · ✅ ${byStatus.done}`);
|
|
703
|
+
});
|
|
663
704
|
withContext(checklist.command('list').description('모든 칸')).option('--owner <owner>', '자리 또는 하위 자리로 거르기').action(async (opts: { owner?: string }, cmd: Command) => {
|
|
664
705
|
const { version, codenames, json } = context(cmd);
|
|
665
706
|
await checklistOutput(version, codenames, 'list', json, opts.owner);
|
|
@@ -742,10 +783,11 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
742
783
|
.option('--ceo-minutes <minutes>', '대표 손 분량(분)').option('--ceo-date <date>', '대표 손 날짜 YYYY-MM-DD (없으면 판 착지일 KST)')
|
|
743
784
|
.option('--accelerator', '가속 등급 — 다른 칸의 처리량을 올리는 칸(자율성·효율성·동시성). 생략하면 등급 없음')
|
|
744
785
|
.option('--allow-duplicate-id', '다른 판에 같은 id 가 있어도 경고 후 추가')
|
|
745
|
-
.
|
|
786
|
+
.option('--ref <path...>', '근거 문서 — 저장소 상대 경로[#절] (반복 가능)')
|
|
787
|
+
.action((id: string, title: string, opts: { ref?: string[]; owner?: string; kind?: string; priority?: string; deadlineVersion?: string; predecessor?: string[]; ceoMinutes?: string; ceoDate?: string; accelerator?: boolean; allowDuplicateId?: boolean }, cmd: Command) => {
|
|
746
788
|
const { version, json } = context(cmd);
|
|
747
789
|
if (!mayWrite(cmd)) return;
|
|
748
|
-
const data = addItem(version, { id, title, ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(opts.kind !== undefined ? { kind: opts.kind as ChecklistKind } : {}), ...(opts.priority !== undefined ? { priority: opts.priority as PlacementPriority } : {}), ...(opts.deadlineVersion !== undefined ? { deadlineVersion: opts.deadlineVersion } : {}), ...(opts.predecessor !== undefined ? { predecessors: opts.predecessor } : {}), ...(opts.ceoMinutes !== undefined ? { ceoMinutes: ceoMinutes(opts.ceoMinutes) } : {}), ...(opts.ceoDate !== undefined ? { ceoDate: opts.ceoDate } : {}), ...(opts.accelerator ? { accelerator: true } : {}) }, { allowDuplicateId: opts.allowDuplicateId });
|
|
790
|
+
const data = addItem(version, { id, title, ...(opts.ref !== undefined ? { refs: opts.ref } : {}), ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(opts.kind !== undefined ? { kind: opts.kind as ChecklistKind } : {}), ...(opts.priority !== undefined ? { priority: opts.priority as PlacementPriority } : {}), ...(opts.deadlineVersion !== undefined ? { deadlineVersion: opts.deadlineVersion } : {}), ...(opts.predecessor !== undefined ? { predecessors: opts.predecessor } : {}), ...(opts.ceoMinutes !== undefined ? { ceoMinutes: ceoMinutes(opts.ceoMinutes) } : {}), ...(opts.ceoDate !== undefined ? { ceoDate: opts.ceoDate } : {}), ...(opts.accelerator ? { accelerator: true } : {}) }, { allowDuplicateId: opts.allowDuplicateId });
|
|
749
791
|
if (json) console.log(JSON.stringify(data)); else console.log(`✅ ${id} 추가`);
|
|
750
792
|
});
|
|
751
793
|
withContext(checklist.command('set <id>').description('칸 상태·근거·담당·처분 갱신'))
|
|
@@ -754,13 +796,14 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
754
796
|
.option('--priority <priority>', 'P0|P1|P2').option('--deadline-version <v>', '마감 판').option('--predecessor <id...>', '선행 칸 id')
|
|
755
797
|
.option('--ceo-minutes <minutes>', '대표 손 분량(분)').option('--ceo-date <date>', '대표 손 날짜 YYYY-MM-DD')
|
|
756
798
|
.option('--accelerator', '가속 등급 부여').option('--no-accelerator', '가속 등급 해제(기본은 없음)')
|
|
757
|
-
.
|
|
799
|
+
.option('--ref <path...>', '근거 문서 더하기 — 저장소 상대 경로[#절] (반복 가능 · 중복 제거)')
|
|
800
|
+
.action((id: string, opts: { ref?: string[]; status?: string; evidence?: string; owner?: string; disposition?: string; kind?: string; priority?: string; deadlineVersion?: string; predecessor?: string[]; ceoMinutes?: string; ceoDate?: string; accelerator?: boolean }, cmd: Command) => {
|
|
758
801
|
const { version, json } = context(cmd);
|
|
759
802
|
if (opts.status !== undefined && !['green', 'yellow', 'red', 'done'].includes(opts.status)) throw new CliUserError(`잘못된 상태: ${opts.status}`, 'green|yellow|red|done');
|
|
760
803
|
if (opts.disposition !== undefined && !['move', 'known-issue', 'block'].includes(opts.disposition)) throw new CliUserError(`잘못된 처분: ${opts.disposition}`, 'move|known-issue|block');
|
|
761
|
-
if (opts.status === undefined && opts.evidence === undefined && opts.owner === undefined && opts.disposition === undefined && opts.kind === undefined && opts.priority === undefined && opts.deadlineVersion === undefined && opts.predecessor === undefined && opts.ceoMinutes === undefined && opts.ceoDate === undefined && opts.accelerator === undefined) throw new CliUserError('갱신할 칸을 지정하라', '--status · --evidence · --owner · --disposition · --kind · --priority · --deadline-version · --predecessor · --ceo-minutes · --ceo-date · --accelerator · --no-accelerator 중 하나');
|
|
804
|
+
if (opts.status === undefined && opts.evidence === undefined && opts.owner === undefined && opts.disposition === undefined && opts.kind === undefined && opts.priority === undefined && opts.deadlineVersion === undefined && opts.predecessor === undefined && opts.ceoMinutes === undefined && opts.ceoDate === undefined && opts.accelerator === undefined && opts.ref === undefined) throw new CliUserError('갱신할 칸을 지정하라', '--ref · --status · --evidence · --owner · --disposition · --kind · --priority · --deadline-version · --predecessor · --ceo-minutes · --ceo-date · --accelerator · --no-accelerator 중 하나');
|
|
762
805
|
if (!mayWrite(cmd)) return;
|
|
763
|
-
const data = setItem(version, id, { ...(opts.status !== undefined ? { status: opts.status as ChecklistStatus } : {}), ...(opts.evidence !== undefined ? { evidence: opts.evidence } : {}), ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(opts.disposition !== undefined ? { disposition: opts.disposition as ChecklistDisposition } : {}), ...(opts.kind !== undefined ? { kind: opts.kind as ChecklistKind } : {}), ...(opts.priority !== undefined ? { priority: opts.priority as PlacementPriority } : {}), ...(opts.deadlineVersion !== undefined ? { deadlineVersion: opts.deadlineVersion } : {}), ...(opts.predecessor !== undefined ? { predecessors: opts.predecessor } : {}), ...(opts.ceoMinutes !== undefined ? { ceoMinutes: ceoMinutes(opts.ceoMinutes) } : {}), ...(opts.ceoDate !== undefined ? { ceoDate: opts.ceoDate } : {}), ...(opts.accelerator !== undefined ? { accelerator: opts.accelerator ? true : null } : {}) }, process.env.ELANOUS_TRACK || 'cli');
|
|
806
|
+
const data = setItem(version, id, { ...(opts.ref !== undefined ? { refs: opts.ref } : {}), ...(opts.status !== undefined ? { status: opts.status as ChecklistStatus } : {}), ...(opts.evidence !== undefined ? { evidence: opts.evidence } : {}), ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(opts.disposition !== undefined ? { disposition: opts.disposition as ChecklistDisposition } : {}), ...(opts.kind !== undefined ? { kind: opts.kind as ChecklistKind } : {}), ...(opts.priority !== undefined ? { priority: opts.priority as PlacementPriority } : {}), ...(opts.deadlineVersion !== undefined ? { deadlineVersion: opts.deadlineVersion } : {}), ...(opts.predecessor !== undefined ? { predecessors: opts.predecessor } : {}), ...(opts.ceoMinutes !== undefined ? { ceoMinutes: ceoMinutes(opts.ceoMinutes) } : {}), ...(opts.ceoDate !== undefined ? { ceoDate: opts.ceoDate } : {}), ...(opts.accelerator !== undefined ? { accelerator: opts.accelerator ? true : null } : {}) }, process.env.ELANOUS_TRACK || 'cli');
|
|
764
807
|
const item = data.items.find((entry) => entry.id === id);
|
|
765
808
|
const why = item?.kind === 'screen' && item.status === 'green' ? parityGap(item.evidence) : null;
|
|
766
809
|
if (why) {
|
|
@@ -909,7 +952,16 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
909
952
|
.action(async (o: { version: string; base: string; pick: string[]; append?: boolean; dryRun?: boolean; json?: boolean }) => {
|
|
910
953
|
await jsonAction(o.json, async (log) => cutReleaseBranch({ ...o, log }), () => true);
|
|
911
954
|
});
|
|
912
|
-
const runAction = async (o: { version: string; cutCommit?: string; dryRun?: boolean; ifReady?: boolean; forceFreeze?: boolean; json?: boolean }, logRun = console.log): Promise<boolean | AutoStartDeferral | undefined> => {
|
|
955
|
+
const runAction = async (o: { version: string; cutCommit?: string; mainCut?: boolean; prerelease?: string; dryRun?: boolean; ifReady?: boolean; forceFreeze?: boolean; json?: boolean }, logRun = console.log): Promise<boolean | AutoStartDeferral | undefined> => {
|
|
956
|
+
// Only the rc rehearsal channel is operated for now (alpha/beta stay valid version shapes, not run options).
|
|
957
|
+
if (o.prerelease !== undefined && o.prerelease !== 'rc') {
|
|
958
|
+
const message = `--prerelease supports rc only: ${o.prerelease}`;
|
|
959
|
+
if (o.json) await writeStdoutJson(`${JSON.stringify({ ok: false, error: message })}\n`);
|
|
960
|
+
else console.error(`⛔ ${message}`);
|
|
961
|
+
process.exitCode = 1;
|
|
962
|
+
return false;
|
|
963
|
+
}
|
|
964
|
+
const prerelease = o.prerelease as PrereleaseKind | undefined;
|
|
913
965
|
if (!o.dryRun) {
|
|
914
966
|
try {
|
|
915
967
|
const frozen = readLandingFreeze(releaseRunDeps.freezeRoot);
|
|
@@ -935,12 +987,13 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
935
987
|
return false;
|
|
936
988
|
}
|
|
937
989
|
}
|
|
938
|
-
|
|
990
|
+
// A prerelease rehearsal never reads the stable version's readiness (checklist cells): it runs directly.
|
|
991
|
+
if (o.ifReady && !o.dryRun && !prerelease) {
|
|
939
992
|
try {
|
|
940
993
|
const outcome = await runReleaseIfReady(o.version, {
|
|
941
994
|
ledgerRoot: releaseRunDeps.ledgerRoot,
|
|
942
995
|
readiness: (version, options) => releaseReadiness(version, { ledgerRoot: releaseRunDeps.ledgerRoot, checklist: releaseRunDeps.checklist, ...options }),
|
|
943
|
-
run: () => runUnattendedRelease({ version: o.version, cutCommit: o.cutCommit, forceFreeze: o.forceFreeze }, releaseRunDeps),
|
|
996
|
+
run: () => runUnattendedRelease({ version: o.version, cutCommit: o.cutCommit, mainCut: o.mainCut, prerelease, forceFreeze: o.forceFreeze }, releaseRunDeps),
|
|
944
997
|
});
|
|
945
998
|
if (outcome.skipped) {
|
|
946
999
|
if (o.json) await writeStdoutJson(`${JSON.stringify(outcome)}\n`);
|
|
@@ -968,7 +1021,7 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
968
1021
|
}
|
|
969
1022
|
}
|
|
970
1023
|
await jsonAction(o.json, async (log) => {
|
|
971
|
-
const result = await runUnattendedRelease({ version: o.version, dryRun: o.dryRun, cutCommit: o.cutCommit, forceFreeze: o.forceFreeze }, releaseRunDeps);
|
|
1024
|
+
const result = await runUnattendedRelease({ version: o.version, dryRun: o.dryRun, cutCommit: o.cutCommit, mainCut: o.mainCut, prerelease, forceFreeze: o.forceFreeze }, releaseRunDeps);
|
|
972
1025
|
log(`${result.dryRun ? '· 드라이런' : '▶ 릴리스 루프'} ${result.input.version} · 입력 ${JSON.stringify(result.input)}`);
|
|
973
1026
|
return result;
|
|
974
1027
|
}, (r) => r.dryRun || r.state?.status === 'done' || r.state?.status === 'awaiting-approval');
|
|
@@ -976,12 +1029,43 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
976
1029
|
release.command('run')
|
|
977
1030
|
.description('릴리스 루프 실행 — 원장 직전 판과 release.loop 설정으로 그래프 입력 구성 · --dry-run 은 입력만 출력')
|
|
978
1031
|
.requiredOption('--version <v>', '공개할 판(x.y.z)')
|
|
979
|
-
.option('--cut-commit <sha>', 'origin/main HEAD 대신 컷으로 쓸 커밋')
|
|
1032
|
+
.option('--cut-commit <sha>', 'origin/main HEAD 대신 컷으로 쓸 커밋(이미 자른 release/<v> 끝 — 옛 경로)')
|
|
1033
|
+
.option('--main-cut', '옛 경로: 판 올림을 main 에 착지(기본은 release/<v> 가지에만 올린다)')
|
|
1034
|
+
.option('--prerelease <kind>', '미리보기 판(rc) — <v>-rc.<다음 빈 번호> · npm next · GitHub pre-release · docs-land/ops-upgrade/dev-bump 건너뜀')
|
|
980
1035
|
.option('--dry-run', '체크리스트·그래프 실행 없이 전체 입력 보기')
|
|
981
1036
|
.option('--if-ready', '준비되지 않았으면 사유를 출력하고 성공으로 건너뛴다')
|
|
982
|
-
.option('--force-freeze', '동결 중 게이트·발행 강행(관측 기록)')
|
|
1037
|
+
.option('--force-freeze', '동결 중 게이트·발행 강행(관측 기록) — 가지 컷은 동결이 필요 없다 · 동결은 비상 스위치로 남는다')
|
|
983
1038
|
.option('--json', '결과 한 줄 JSON(stdout) · 사람 줄은 stderr')
|
|
984
|
-
.action(async (o: { version: string; cutCommit?: string; dryRun?: boolean; ifReady?: boolean; forceFreeze?: boolean; json?: boolean }) => { await runAction(o); });
|
|
1039
|
+
.action(async (o: { version: string; cutCommit?: string; mainCut?: boolean; prerelease?: string; dryRun?: boolean; ifReady?: boolean; forceFreeze?: boolean; json?: boolean }) => { await runAction(o); });
|
|
1040
|
+
release.command('preflight')
|
|
1041
|
+
.description('컷 30분 전 사전 점검(읽기 전용) — 체크리스트 게이트 모의 · 발행 뒤 칸 · run --dry-run 입력 · 동결 · 일정/게이트 실측. ⛔ 있으면 exit 1')
|
|
1042
|
+
.requiredOption('--version <v>', '점검할 판(x.y.z)')
|
|
1043
|
+
.option('--publish-at <iso>', '계획 발행 시각(기본: 컷 뒤 첫 07·18시 KST)')
|
|
1044
|
+
.option('--json', '결과 한 줄 JSON(stdout)')
|
|
1045
|
+
.action(async (o: { version: string; publishAt?: string; json?: boolean }) => {
|
|
1046
|
+
try {
|
|
1047
|
+
const result = await releasePreflight(o.version, { publishAt: o.publishAt, releaseRunDeps });
|
|
1048
|
+
if (o.json) await writeStdoutJson(`${JSON.stringify(result)}\n`);
|
|
1049
|
+
else for (const line of formatPreflight(result)) console.log(line);
|
|
1050
|
+
if (result.blockers > 0) process.exitCode = 1;
|
|
1051
|
+
} catch (e) {
|
|
1052
|
+
if (o.json) await writeStdoutJson(`${JSON.stringify({ ok: false, error: (e as Error).message })}\n`);
|
|
1053
|
+
else console.error(`⛔ ${(e as Error).message}`);
|
|
1054
|
+
process.exitCode = 1;
|
|
1055
|
+
}
|
|
1056
|
+
});
|
|
1057
|
+
release.command('resume')
|
|
1058
|
+
.description('실패한 릴리스 런을 이어 달린다 — cut-branch --append 로 고친 release/<v> 새 끝을 version-release 기록에 반영(빨리감기만·상태 백업) 뒤 --from 노드부터')
|
|
1059
|
+
.requiredOption('--run <runId>', '실패한 release-loop 런 ID')
|
|
1060
|
+
.requiredOption('--from <node>', '다시 시작할 노드(예: gate)')
|
|
1061
|
+
.option('--json', '결과 한 줄 JSON(stdout)')
|
|
1062
|
+
.action(async (o: { run: string; from: string; json?: boolean }) => {
|
|
1063
|
+
await jsonAction(o.json, async (log) => {
|
|
1064
|
+
const result = await resumeReleaseRun({ runId: o.run, from: o.from });
|
|
1065
|
+
log(`${result.tip.changed ? `↻ ${result.tip.branch} ${result.tip.from.slice(0, 12)} → ${result.tip.to.slice(0, 12)} (백업 ${result.tip.backup})` : `· ${result.tip.branch} 끝 그대로 ${result.tip.to.slice(0, 12)}`} · ${o.from} 부터 → ${result.state.status}`);
|
|
1066
|
+
return { tip: result.tip, status: result.state.status, runId: result.state.runId };
|
|
1067
|
+
}, (r) => r.status === 'done' || r.status === 'awaiting-approval');
|
|
1068
|
+
});
|
|
985
1069
|
release.command('auto-start')
|
|
986
1070
|
.description('판 일정의 컷 창에서 릴리스 시작을 미리 본다 · --apply 로 release run --if-ready 실행')
|
|
987
1071
|
.option('--window-minutes <n>', '컷 뒤 선택 창(분)', '15')
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export type EdgeRailDecision = 'hold' | 'canary-install' | 'promote-all' | 'rollback' | 'wait-quiet';
|
|
2
|
+
export interface EdgeRailPlan { decision: EdgeRailDecision; reason: string }
|
|
3
|
+
export interface EdgeRailInput {
|
|
4
|
+
now: Date;
|
|
5
|
+
mainCommit: string;
|
|
6
|
+
installed: string;
|
|
7
|
+
seat: string;
|
|
8
|
+
canary: { seat: string; okRuns: number; startedAt: string } | null;
|
|
9
|
+
failureRate: { before: number; after: number; samples: number } | null;
|
|
10
|
+
quietWindow: boolean;
|
|
11
|
+
canaryOkRuns?: number;
|
|
12
|
+
failureMultiplier?: number;
|
|
13
|
+
minSamples?: number;
|
|
14
|
+
promoted?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** No I/O: unknown health, insufficient samples and unknown revision never trigger a cutover. */
|
|
18
|
+
export function planEdgeRail(input: EdgeRailInput): EdgeRailPlan {
|
|
19
|
+
const { mainCommit, installed, seat, canary, failureRate, quietWindow } = input;
|
|
20
|
+
const okRuns = input.canaryOkRuns ?? 3;
|
|
21
|
+
const multiplier = input.failureMultiplier ?? 2;
|
|
22
|
+
const minSamples = input.minSamples ?? 30;
|
|
23
|
+
if (!Number.isFinite(input.now.getTime()) || !/^[a-f0-9]{12,40}$/i.test(mainCommit) || !/^[a-f0-9]{12,40}$/i.test(installed)
|
|
24
|
+
|| !Number.isSafeInteger(okRuns) || okRuns < 1 || !Number.isFinite(multiplier) || multiplier <= 1
|
|
25
|
+
|| !Number.isSafeInteger(minSamples) || minSamples < 1) return { decision: 'hold', reason: 'invalid or unknown rollout input' };
|
|
26
|
+
const same = mainCommit.startsWith(installed) || installed.startsWith(mainCommit);
|
|
27
|
+
if (canary && Date.parse(canary.startedAt) > input.now.getTime()) return { decision: 'hold', reason: 'canary started in the future' };
|
|
28
|
+
if (canary && (canary.seat !== 'OP' || !Number.isSafeInteger(canary.okRuns) || canary.okRuns < 0
|
|
29
|
+
|| !Number.isFinite(Date.parse(canary.startedAt)))) return { decision: 'hold', reason: 'invalid canary state' };
|
|
30
|
+
if (failureRate) {
|
|
31
|
+
if (![failureRate.before, failureRate.after, failureRate.samples].every(Number.isFinite)
|
|
32
|
+
|| failureRate.before < 0 || failureRate.after < 0 || failureRate.before > 1 || failureRate.after > 1
|
|
33
|
+
|| !Number.isSafeInteger(failureRate.samples) || failureRate.samples < 0) return { decision: 'hold', reason: 'invalid failure-rate measurement' };
|
|
34
|
+
if (failureRate.samples < minSamples) return { decision: 'hold', reason: `failure-rate samples ${failureRate.samples} < ${minSamples}` };
|
|
35
|
+
if (same && canary && failureRate.after > 0 && failureRate.after >= failureRate.before * multiplier) return quietWindow
|
|
36
|
+
? { decision: 'rollback', reason: `failure rate ${failureRate.after} >= ${multiplier}x ${failureRate.before}` }
|
|
37
|
+
: { decision: 'wait-quiet', reason: 'rollback requires quiet window for resident restart' };
|
|
38
|
+
}
|
|
39
|
+
if (canary && canary.okRuns < okRuns) return { decision: 'hold', reason: `OP canary ${canary.okRuns}/${okRuns} successful runs` };
|
|
40
|
+
if (same) return { decision: 'hold', reason: 'installed commit already matches main' };
|
|
41
|
+
if (input.promoted && canary && seat !== 'OP') return { decision: 'hold', reason: 'fleet promotion already completed' };
|
|
42
|
+
if (!canary && seat !== 'OP') return { decision: 'hold', reason: 'OP canary has not installed' };
|
|
43
|
+
if (!quietWindow) return { decision: 'wait-quiet', reason: 'resident restart requires quiet window' };
|
|
44
|
+
if (!canary) return { decision: 'canary-install', reason: 'install OP canary first' };
|
|
45
|
+
return { decision: 'promote-all', reason: `OP canary passed ${canary.okRuns}/${okRuns} successful runs` };
|
|
46
|
+
}
|