tumwater 0.1.2 → 0.2.0
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/README.md +74 -108
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +24 -0
- package/dist/src/agent/agent-backend.js +2 -2
- package/dist/src/agent/backend-traits.js +60 -0
- package/dist/src/agent/claude/claude-account.js +61 -8
- package/dist/src/agent/claude/claude-args.js +12 -15
- package/dist/src/agent/claude/claude-backend.js +51 -41
- package/dist/src/agent/claude/claude-env.js +7 -3
- package/dist/src/agent/claude/claude-stream.js +7 -16
- package/dist/src/agent/image-input.js +107 -0
- package/dist/src/agent/notes-shim.js +41 -0
- package/dist/src/agent/opencode/opencode-args.js +61 -0
- package/dist/src/agent/opencode/opencode-backend.js +255 -0
- package/dist/src/agent/opencode/opencode-catalog.js +184 -0
- package/dist/src/agent/opencode/opencode-config.js +188 -0
- package/dist/src/agent/opencode/opencode-env.js +36 -0
- package/dist/src/agent/opencode/opencode-session-id.js +37 -0
- package/dist/src/agent/opencode/opencode-stream.js +278 -0
- package/dist/src/agent/role-notes-write.js +16 -32
- package/dist/src/agent/run-agent.js +95 -18
- package/dist/src/agent/session-ledger.js +57 -1
- package/dist/src/backlog/backlog-archive.js +296 -0
- package/dist/src/backlog/backlog-cli.js +573 -0
- package/dist/src/backlog/backlog-commit.js +31 -0
- package/dist/src/backlog/backlog-eligibility.js +357 -33
- package/dist/src/backlog/backlog-md.js +98 -18
- package/dist/src/backlog/backlog-render.js +62 -11
- package/dist/src/backlog/backlog-structure.js +261 -58
- package/dist/src/backlog/backlog-write.js +197 -319
- package/dist/src/backlog/backlog.js +113 -16
- package/dist/src/backlog/question-answer.js +339 -0
- package/dist/src/baseline/main-baseline.js +75 -88
- package/dist/src/baseline/main-red.js +38 -30
- package/dist/src/brief/brief-edit.js +43 -0
- package/dist/src/{brief.js → brief/brief.js} +66 -10
- package/dist/src/budget/budget.js +32 -15
- package/dist/src/budget/fallback-breaker.js +8 -7
- package/dist/src/build/build-check-detect.js +8 -7
- package/dist/src/build/build-check-events.js +68 -22
- package/dist/src/build/build-check-report.js +22 -23
- package/dist/src/build/build-check-scoped.js +51 -62
- package/dist/src/build/build-check.js +15 -14
- package/dist/src/build/build-info.js +2 -1
- package/dist/src/build/build-stage.js +23 -22
- package/dist/src/build/dep-install.js +22 -21
- package/dist/src/build/host-sleep.js +1 -1
- package/dist/src/change/change-data.js +25 -14
- package/dist/src/change/change-render.js +11 -10
- package/dist/src/cli/brief-command.js +58 -0
- package/dist/src/cli/cli-args.js +25 -5
- package/dist/src/cli/cli-command-args.js +46 -14
- package/dist/src/cli/cli-editor.js +64 -0
- package/dist/src/cli/cli-flag-specs.js +54 -16
- package/dist/src/cli/cli-marker-commands.js +13 -10
- package/dist/src/cli/cli-output.js +9 -0
- package/dist/src/cli/cli-query-commands.js +458 -49
- package/dist/src/cli/cli-repo-gate.js +1 -1
- package/dist/src/cli/cli-run.js +208 -59
- package/dist/src/cli/completion.js +207 -0
- package/dist/src/cli/config-commands.js +14 -11
- package/dist/src/cli/help.js +305 -110
- package/dist/src/cli/log-commands.js +127 -21
- package/dist/src/cli/notify-command.js +33 -18
- package/dist/src/cli/question-commands.js +29 -224
- package/dist/src/cli.js +121 -40
- package/dist/src/collections.js +66 -16
- package/dist/src/concurrency/check-permit.js +16 -17
- package/dist/src/concurrency/lock.js +6 -5
- package/dist/src/concurrency/provider-lease.js +69 -0
- package/dist/src/concurrency/semaphore.js +258 -36
- package/dist/src/config/config-aliases.js +106 -0
- package/dist/src/config/config-editable-keys.js +3 -3
- package/dist/src/config/config-fallbacks.js +379 -0
- package/dist/src/config/config-field-checks.js +19 -10
- package/dist/src/config/config-keys.js +160 -0
- package/dist/src/config/config-live.js +64 -12
- package/dist/src/config/config-schema.js +34 -22
- package/dist/src/config/config-validation.js +132 -48
- package/dist/src/config/config-views.js +119 -337
- package/dist/src/config/config-write.js +138 -177
- package/dist/src/config/config.js +60 -49
- package/dist/src/config/model-selector.js +28 -2
- package/dist/src/doctor/doctor-backlog.js +546 -49
- package/dist/src/doctor/doctor-checks.js +534 -56
- package/dist/src/doctor/doctor-holds.js +193 -53
- package/dist/src/doctor/doctor-install.js +30 -0
- package/dist/src/doctor/doctor-launch-services.js +2 -2
- package/dist/src/doctor/doctor-model-checks.js +549 -157
- package/dist/src/doctor/doctor-notify.js +27 -0
- package/dist/src/doctor/doctor-orphans.js +10 -9
- package/dist/src/doctor/doctor-render.js +36 -9
- package/dist/src/doctor/doctor.js +80 -22
- package/dist/src/doctor/image-probe.js +244 -0
- package/dist/src/doctor/probe.js +64 -0
- package/dist/src/events/event-format.js +226 -102
- package/dist/src/events/event-labels.js +116 -0
- package/dist/src/events/event-legacy.js +140 -0
- package/dist/src/events/event-read.js +58 -22
- package/dist/src/events/event-window.js +27 -7
- package/dist/src/events/events.js +18 -14
- package/dist/src/events/notify.js +137 -38
- package/dist/src/failure/failure-cluster.js +12 -9
- package/dist/src/failure/failure-data.js +96 -48
- package/dist/src/failure/failure-render.js +69 -58
- package/dist/src/failure/failure-state-change.js +33 -26
- package/dist/src/failure/time-spend.js +79 -59
- package/dist/src/files/file-queue.js +16 -4
- package/dist/src/files/files.js +18 -6
- package/dist/src/files/json-object.js +18 -3
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +5 -4
- package/dist/src/gates/budget-gates.js +250 -34
- package/dist/src/gates/disk-gate.js +18 -18
- package/dist/src/gates/gate-polls.js +117 -69
- package/dist/src/gates/maintenance-quota.js +29 -29
- package/dist/src/gates/pause-gates.js +11 -11
- package/dist/src/gates/readiness.js +2 -1
- package/dist/src/gates/role-cap-gates.js +16 -21
- package/dist/src/gates/startup-gate.js +75 -31
- package/dist/src/gates/streak-gate.js +18 -16
- package/dist/src/git/commit-message.js +12 -5
- package/dist/src/git/git-diff.js +16 -16
- package/dist/src/git/git-run.js +8 -8
- package/dist/src/git/git.js +86 -55
- package/dist/src/git/slots-state.js +14 -5
- package/dist/src/git/worktree-pool.js +25 -18
- package/dist/src/git/worktree-use.js +15 -14
- package/dist/src/git/worktree.js +18 -18
- package/dist/src/gui/gui-args.js +10 -14
- package/dist/src/gui/gui-backlog-commands.js +273 -0
- package/dist/src/gui/gui-command.js +1 -1
- package/dist/src/gui/gui-config-commands.js +440 -0
- package/dist/src/gui/gui-endpoint-commands.js +16 -669
- package/dist/src/gui/gui-endpoint-common.js +49 -0
- package/dist/src/gui/gui-endpoints.js +116 -56
- package/dist/src/gui/gui-operator-commands.js +145 -0
- package/dist/src/gui/gui-prompt-commands.js +234 -0
- package/dist/src/gui/gui-server.js +28 -8
- package/dist/src/gui/gui-team-commands.js +164 -0
- package/dist/src/gui/http-body.js +17 -1
- package/dist/src/history/history-data.js +194 -98
- package/dist/src/history/history-render.js +76 -0
- package/dist/src/history/history.js +37 -58
- package/dist/src/inbox/inbox-attachments.js +75 -14
- package/dist/src/inbox/inbox-cancel.js +7 -5
- package/dist/src/inbox/inbox-edit.js +1 -1
- package/dist/src/inbox/inbox-submit.js +44 -4
- package/dist/src/inbox/inbox-top.js +83 -0
- package/dist/src/inbox/inbox.js +8 -2
- package/dist/src/inbox/pending-prompt.js +1 -1
- package/dist/src/inbox/prompt-commands.js +170 -80
- package/dist/src/init/init-templates.js +16 -14
- package/dist/src/init/init.js +12 -11
- package/dist/src/loop/leftover.js +28 -27
- package/dist/src/loop/loop-fallback.js +2 -2
- package/dist/src/loop/loop-pi.js +106 -52
- package/dist/src/loop/loop-recovery.js +15 -15
- package/dist/src/loop/loop-state.js +139 -13
- package/dist/src/loop/loop-tick.js +194 -86
- package/dist/src/loop/loop.js +72 -36
- package/dist/src/loop/model-fallback.js +3 -2
- package/dist/src/loop/revision.js +16 -7
- package/dist/src/merge/backlog-conflicts.js +155 -0
- package/dist/src/{landing/landing-batch.js → merge/merge-batch.js} +90 -89
- package/dist/src/{landing/landing-check-failures.js → merge/merge-check-failures.js} +87 -41
- package/dist/src/{landing/landing-core.js → merge/merge-core.js} +119 -95
- package/dist/src/{landing/landing-diff.js → merge/merge-diff.js} +1 -1
- package/dist/src/merge/merge-drain.js +212 -0
- package/dist/src/{landing/landing-git.js → merge/merge-git.js} +94 -21
- package/dist/src/merge/merge-pipeline.js +99 -0
- package/dist/src/{landing/landing-questions.js → merge/merge-questions.js} +9 -6
- package/dist/src/merge/merge-queue-render.js +75 -0
- package/dist/src/{landing/landing-queue.js → merge/merge-queue.js} +95 -36
- package/dist/src/merge/merge-slot.js +340 -0
- package/dist/src/{landing/landing-stack.js → merge/merge-stack.js} +66 -62
- package/dist/src/{landing/landing-merge.js → merge/merge-to-main.js} +72 -62
- package/dist/src/merge/merge-verify.js +212 -0
- package/dist/src/notes-cli.js +97 -0
- package/dist/src/operator/operator-commands.js +154 -47
- package/dist/src/operator/operator-intent.js +87 -49
- package/dist/src/operator/operator-requests.js +52 -52
- package/dist/src/operator/retire.js +62 -27
- package/dist/src/operator/revert.js +71 -0
- package/dist/src/orchestrator/orchestrator-launch.js +74 -14
- package/dist/src/orchestrator/orchestrator-scheduling.js +129 -44
- package/dist/src/orchestrator/orchestrator.js +158 -100
- package/dist/src/orchestrator/retention.js +29 -12
- package/dist/src/orchestrator/startup-migration-steps.js +51 -0
- package/dist/src/orchestrator/startup-migrations.js +40 -0
- package/dist/src/paths.js +128 -37
- package/dist/src/pi/pi-args.js +26 -3
- package/dist/src/pi/pi-backend.js +15 -23
- package/dist/src/pi/pi-bin.js +18 -12
- package/dist/src/pi/pi-event-line.js +17 -1
- package/dist/src/pi/pi-failure.js +87 -0
- package/dist/src/pi/pi-models.js +126 -38
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +86 -0
- package/dist/src/pi/pi-stream.js +131 -107
- package/dist/src/pi/pi-watchdogs.js +47 -43
- package/dist/src/pi-extension/bounded-output.js +20 -5
- package/dist/src/pi-extension/context-budget.js +3 -1
- package/dist/src/pi-extension/context-shake.js +54 -21
- package/dist/src/pi-extension/context-window.js +37 -0
- package/dist/src/pi-extension/full-output.js +2 -2
- package/dist/src/pi-extension/strip-thinking.js +80 -0
- package/dist/src/pi-extension/trim-system-prompt.js +38 -0
- package/dist/src/process/launch-services.js +11 -10
- package/dist/src/process/process-table.js +1 -1
- package/dist/src/process/process.js +7 -7
- package/dist/src/process/run-marker.js +29 -6
- package/dist/src/process/supervisor.js +47 -25
- package/dist/src/prompt/dialect.js +30 -10
- package/dist/src/prompt/gate-prompts.js +170 -67
- package/dist/src/prompt/principles.js +1 -0
- package/dist/src/prompt/prompt-followup.js +35 -20
- package/dist/src/prompt/prompt.js +249 -91
- package/dist/src/redeploy/redeploy-policy.js +10 -10
- package/dist/src/redeploy/redeploy.js +6 -4
- package/dist/src/redeploy/redeployer.js +63 -55
- package/dist/src/redeploy/self-reload.js +1 -1
- package/dist/src/report/report-data.js +264 -255
- package/dist/src/report/report-fold.js +353 -0
- package/dist/src/report/report-render.js +270 -33
- package/dist/src/request-timeouts.js +1 -1
- package/dist/src/review/known-flakes.js +34 -14
- package/dist/src/review/protected-edits.js +33 -0
- package/dist/src/review/review-followup.js +16 -4
- package/dist/src/review/review-precheck.js +14 -13
- package/dist/src/review/review-verdict.js +14 -0
- package/dist/src/review/review.js +81 -39
- package/dist/src/review/secret-leaks.js +127 -0
- package/dist/src/review/suite-rerun.js +4 -3
- package/dist/src/roles/legacy-role-ids.js +21 -0
- package/dist/src/roles/legacy-role-migration.js +62 -0
- package/dist/src/roles/loop-ids.js +19 -2
- package/dist/src/roles/role-catalog.js +125 -135
- package/dist/src/roles/role-guidance.js +64 -31
- package/dist/src/roles/role-labels.js +10 -0
- package/dist/src/roles/role-render.js +194 -18
- package/dist/src/roles/role-view.js +216 -17
- package/dist/src/roles/roles.js +18 -15
- package/dist/src/scheduling/backoff.js +29 -19
- package/dist/src/scheduling/claims.js +25 -9
- package/dist/src/scheduling/once-round.js +11 -11
- package/dist/src/scheduling/quiet-hours.js +25 -32
- package/dist/src/scheduling/scheduling.js +43 -24
- package/dist/src/scheduling/{work-landed-cache.js → work-merged-cache.js} +14 -14
- package/dist/src/status/status-budget.js +115 -0
- package/dist/src/status/status-data.js +77 -184
- package/dist/src/status/status-inbox.js +47 -0
- package/dist/src/status/status-install.js +27 -0
- package/dist/src/status/status-polls.js +68 -33
- package/dist/src/status/status-roles.js +105 -0
- package/dist/src/{fleet → team}/error-storm.js +10 -10
- package/dist/src/{fleet → team}/failure-spread.js +8 -8
- package/dist/src/{fleet → team}/orchestrator-info.js +86 -18
- package/dist/src/{fleet → team}/reclaim.js +5 -4
- package/dist/src/{fleet/fleet-hold.js → team/team-hold.js} +55 -21
- package/dist/src/{fleet/fleet-polls.js → team/team-polls.js} +116 -65
- package/dist/src/{fleet/fleet-state.js → team/team-state.js} +62 -37
- package/dist/src/text/datetime.js +42 -2
- package/dist/src/text/format.js +10 -1
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +35 -34
- package/dist/src/text/suggest.js +3 -2
- package/dist/src/text/text.js +2 -2
- package/dist/src/tick/qa-coverage.js +2 -1
- package/dist/src/tick/stage-check.js +147 -26
- package/dist/src/tick/telemetry-digest.js +10 -9
- package/dist/src/tick/tick-apply.js +101 -72
- package/dist/src/tick/tick-detail-data.js +32 -18
- package/dist/src/tick/tick-detail.js +62 -21
- package/dist/src/tick/tick-finalize.js +29 -22
- package/dist/src/tick/tick-outcome.js +2 -1
- package/dist/src/tick/tick-prompt.js +94 -26
- package/dist/src/tick/tick-resume.js +29 -13
- package/dist/src/tick/tick-stage.js +91 -47
- package/dist/src/tick/tick-timing.js +60 -59
- package/dist/src/tick/tick-usage.js +53 -23
- package/dist/src/tick/tick-verdict.js +76 -10
- package/dist/src/ui/badges.js +118 -67
- package/dist/src/ui/gui/gui-client-boot.js +209 -30
- package/dist/src/ui/gui/gui-client-composer.js +102 -23
- package/dist/src/ui/gui/gui-client-drawer.js +330 -75
- package/dist/src/ui/gui/gui-client-history.js +96 -38
- package/dist/src/ui/gui/gui-client-labels.js +24 -0
- package/dist/src/ui/gui/gui-client-loops.js +94 -52
- package/dist/src/ui/gui/gui-client-markdown.js +1 -0
- package/dist/src/ui/gui/gui-client-model.js +63 -43
- package/dist/src/ui/gui/gui-client-operator.js +129 -39
- package/dist/src/ui/gui/gui-client-pending.js +36 -32
- package/dist/src/ui/gui/gui-client-report.js +339 -46
- package/dist/src/ui/gui/gui-client-settings.js +148 -14
- package/dist/src/ui/gui/gui-client-sound.js +2 -1
- package/dist/src/ui/gui/{gui-client-fleet.js → gui-client-team.js} +196 -72
- package/dist/src/ui/gui/gui-client.js +26 -20
- package/dist/src/ui/gui/gui-page.js +19 -19
- package/dist/src/ui/gui/gui-styles.js +12 -4
- package/dist/src/ui/progress-data.js +5 -3
- package/dist/src/ui/status-model.js +184 -122
- package/dist/src/ui/status-payload.js +119 -32
- package/dist/src/ui/status-render.js +122 -52
- package/dist/src/ui/team-alerts.js +341 -0
- package/dist/src/ui/tick-progress-model.js +14 -14
- package/dist/src/ui/tone.js +11 -8
- package/dist/src/ui/transcript-tail.js +1 -0
- package/dist/src/ui/transcript.js +23 -16
- package/dist/src/ui/tui/tui-app.js +1 -0
- package/dist/src/ui/tui/tui-backlog.js +85 -11
- package/dist/src/ui/tui/tui-editor-answer.js +97 -0
- package/dist/src/ui/tui/tui-editor-attach.js +107 -0
- package/dist/src/ui/tui/tui-editor-brief.js +107 -0
- package/dist/src/ui/tui/tui-editor-budget.js +155 -0
- package/dist/src/ui/tui/tui-editor-ctx.js +1 -0
- package/dist/src/ui/tui/tui-editor-entry.js +230 -0
- package/dist/src/ui/tui/tui-editor-interval.js +122 -0
- package/dist/src/ui/tui/tui-editor-notes.js +114 -0
- package/dist/src/ui/tui/tui-editor-pause.js +266 -0
- package/dist/src/ui/tui/tui-editor-prompt.js +95 -0
- package/dist/src/ui/tui/tui-editor-queued.js +98 -0
- package/dist/src/ui/tui/tui-editor-quiet.js +106 -0
- package/dist/src/ui/tui/tui-editor-retire.js +83 -0
- package/dist/src/ui/tui/tui-editor-search.js +73 -0
- package/dist/src/ui/tui/tui-editor-session.js +97 -0
- package/dist/src/ui/tui/tui-editors.js +152 -0
- package/dist/src/ui/tui/tui-frame.js +67 -55
- package/dist/src/ui/tui/tui-history.js +12 -7
- package/dist/src/ui/tui/tui-input.js +49 -44
- package/dist/src/ui/tui/tui-keymap.js +1 -0
- package/dist/src/ui/tui/tui-keys.js +91 -1704
- package/dist/src/ui/tui/tui-pane-activity.js +99 -0
- package/dist/src/ui/tui/tui-pane-backlog.js +224 -0
- package/dist/src/ui/tui/tui-pane-brief.js +30 -0
- package/dist/src/ui/tui/tui-pane-ctx.js +11 -0
- package/dist/src/ui/tui/tui-pane-health.js +124 -0
- package/dist/src/ui/tui/tui-pane-history.js +316 -0
- package/dist/src/ui/tui/tui-pane-pending.js +74 -0
- package/dist/src/ui/tui/tui-pane-queued.js +143 -0
- package/dist/src/ui/tui/tui-pane-report.js +122 -0
- package/dist/src/ui/tui/tui-pane-session.js +101 -0
- package/dist/src/ui/tui/tui-pane-transcript.js +150 -0
- package/dist/src/ui/tui/tui-pane.js +66 -28
- package/dist/src/ui/tui/tui-panes.js +294 -0
- package/dist/src/ui/tui/tui.js +45 -25
- package/dist/src/verdict/fix-claim.js +141 -38
- package/dist/src/verdict/no-change.js +19 -0
- package/dist/src/verdict/refusal.js +92 -8
- package/dist/src/verdict/reply-contract.js +17 -5
- package/dist/src/version.js +3 -2
- package/package.json +4 -2
- package/dist/src/agent/mcp/role-notes-server.js +0 -160
- package/dist/src/landing/backlog-conflicts.js +0 -116
- package/dist/src/landing/landing-drain.js +0 -204
- package/dist/src/landing/landing-pipeline.js +0 -96
- package/dist/src/landing/landing-queue-render.js +0 -31
- package/dist/src/landing/landing-slot.js +0 -246
- package/dist/src/landing/landing-vetting.js +0 -196
- package/dist/src/pi-extension/role-notes.js +0 -40
- package/dist/src/ui/fleet-alerts.js +0 -174
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
1
2
|
import { buildStaleness, distDir, isSelfHosted, readBuildInfo } from "../build/build-info.js";
|
|
3
|
+
import { TUMWATER_SOURCE } from "../events/events.js";
|
|
2
4
|
import { baselineCheckEventLogger } from "../build/build-check-events.js";
|
|
3
5
|
import { liveConfig } from "../config/config.js";
|
|
4
6
|
import { cachedBaselineVerdict, checkMainBaseline, mainIsGreen } from "../baseline/main-baseline.js";
|
|
@@ -46,24 +48,24 @@ export function redeployDeps(root, build, log, bootProblem) {
|
|
|
46
48
|
mainGreen: async (mainHead) =>
|
|
47
49
|
// The live config per call (a mid-run edit applies to the next green check like it
|
|
48
50
|
// does everywhere else); a broken file degrades to defaults — no declared check.
|
|
49
|
-
mainIsGreen(await mirror(mainHead), liveConfig(root), baselineCheckEventLogger(
|
|
51
|
+
mainIsGreen(await mirror(mainHead), liveConfig(root), baselineCheckEventLogger(TUMWATER_SOURCE, safeLog)),
|
|
50
52
|
compile: async (mainHead) => compileStaged(root, await mirror(mainHead), mainHead),
|
|
51
53
|
swap: (mainHead) => swapDist(root, dist, mainHead),
|
|
52
54
|
bootProblem,
|
|
53
55
|
// The urgency carve-out's verdict (BUGS.md 2026-09-30): a cached verdict answers free;
|
|
54
56
|
// otherwise the baseline check runs on the running build's own SHA, in a dedicated witness
|
|
55
57
|
// worktree — never the mirror, which the green check and compile serve at main's heads —
|
|
56
|
-
// and lands in the same
|
|
58
|
+
// and lands in the same team-shared cache every later consult reads. A fresh red is
|
|
57
59
|
// provisional (BUGS.md 2026-09-30): the gate's flake rule applies here too, so one
|
|
58
60
|
// immediate re-run must confirm it before the carve-out may cut the cooldown on it — a
|
|
59
|
-
// green on the re-run promotes the SHA
|
|
61
|
+
// green on the re-run promotes the SHA team-wide, and a skipped re-run leaves the verdict
|
|
60
62
|
// unsettled (null), which the policy reads as not-red.
|
|
61
63
|
buildRed: async (buildSha) => {
|
|
62
64
|
const cached = cachedBaselineVerdict(buildSha);
|
|
63
65
|
if (cached !== undefined)
|
|
64
66
|
return cached === "red";
|
|
65
67
|
const witness = await ensureDetachedWorktree(root, witnessWorktreePath(root), buildSha);
|
|
66
|
-
const onRun = baselineCheckEventLogger(
|
|
68
|
+
const onRun = baselineCheckEventLogger(TUMWATER_SOURCE, safeLog);
|
|
67
69
|
const baseline = await checkMainBaseline(witness, liveConfig(root), onRun);
|
|
68
70
|
if (baseline.baseline?.status !== "red") {
|
|
69
71
|
return baseline.baseline ? false : null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TUMWATER_SOURCE } from "../events/events.js";
|
|
1
2
|
import { errorMessage, stripTerminalControls } from "../text/text.js";
|
|
2
3
|
import { shortSha } from "../text/format.js";
|
|
3
4
|
import { mainRedPhrase } from "../text/phrases.js";
|
|
@@ -7,7 +8,7 @@ import { PrewarmProbes, track } from "./redeploy-probes.js";
|
|
|
7
8
|
* `poll` runs inside the orchestrator's catch-less poll loop and `forceRestart` inside the
|
|
8
9
|
* restart-marker consumer's, so a `logEvent` throw (ENOSPC, EACCES, the path replaced by a
|
|
9
10
|
* directory) on the first build_stale/restart_refused/restart_blocked transition would end the
|
|
10
|
-
*
|
|
11
|
+
* team, and for a forced restart leave its marker unacknowledged — the same "logging must not
|
|
11
12
|
* throw" policy the orchestrator poll body's `emit` and the abort-request consumer apply. The
|
|
12
13
|
* redeploy wiring (redeploy.ts's redeployDeps) wraps its sink with this too: the green and
|
|
13
14
|
* witness checks call that sink from inside checkMainBaseline's run hook, whose throw would
|
|
@@ -23,7 +24,7 @@ export function bestEffortLog(log) {
|
|
|
23
24
|
catch (err) {
|
|
24
25
|
// A terminal boundary: the error text can carry control characters. Sanitize like
|
|
25
26
|
// cli-output's say()/fail(), so the fallback line cannot drive the operator's shell.
|
|
26
|
-
process.stderr.write(`tumwater:
|
|
27
|
+
process.stderr.write(`tumwater: could not record ${event.type} event: ${stripTerminalControls(errorMessage(err))}\n`);
|
|
27
28
|
}
|
|
28
29
|
};
|
|
29
30
|
}
|
|
@@ -40,15 +41,15 @@ export class Redeployer {
|
|
|
40
41
|
staleness = null;
|
|
41
42
|
/** The head a restart is pending for. */
|
|
42
43
|
pendingHead = null;
|
|
43
|
-
/** When the
|
|
44
|
+
/** When the team's current unbroken hold began — 0 when it is not being held. The drain
|
|
44
45
|
* deadline is measured from here and NOT from when the current head became pending: a busy
|
|
45
|
-
* self-hosting
|
|
46
|
+
* self-hosting team merges while it drains, and giving each new head its own full window
|
|
46
47
|
* handed the same in-flight ticks another 30 minutes every time main moved. On 2026-09-08 that
|
|
47
|
-
* held the
|
|
48
|
+
* held the team for 38 minutes under a 30-minute cap and reported the last 30 (BUGS.md).
|
|
48
49
|
* Nothing new starts during a hold, so the ticks a drain waits on can only be the ones it
|
|
49
50
|
* began with — one window is all they are owed. */
|
|
50
51
|
drainSince = 0;
|
|
51
|
-
/** The drain window captured when the current hold began: the
|
|
52
|
+
/** The drain window captured when the current hold began: the team's observed p75 tick
|
|
52
53
|
* duration when the orchestrator has enough samples, else the cold-start RESTART_DRAIN_MAX_MS.
|
|
53
54
|
* Captured once per unbroken hold so a sample change mid-episode cannot move a deadline that
|
|
54
55
|
* is already running (see drainSince). */
|
|
@@ -82,7 +83,7 @@ export class Redeployer {
|
|
|
82
83
|
forcedRestart = false;
|
|
83
84
|
/** When the current unbroken stale episode began (epoch ms), or null while the build is fresh.
|
|
84
85
|
* Deliberately head-independent: main moving under a stale build CONTINUES the episode rather
|
|
85
|
-
* than restarting the clock, because the
|
|
86
|
+
* than restarting the clock, because the team's predicament — running code main has left
|
|
86
87
|
* behind — is the same whatever the head, and a per-head clock is exactly what let the
|
|
87
88
|
* 2026-09-29 pin produce 109 warnings without ever saying the pin was the story. In-memory,
|
|
88
89
|
* per process: a completed restart (and the process exit that follows it) is a new world. */
|
|
@@ -97,7 +98,7 @@ export class Redeployer {
|
|
|
97
98
|
lastEscalationAt = null;
|
|
98
99
|
/** The cooldown deadline the current episode's deferral was last warned about — one warning
|
|
99
100
|
* per distinct deadline, not one per poll and not one per head: the cooldown condition is
|
|
100
|
-
* head-independent, so a
|
|
101
|
+
* head-independent, so a merge mid-cooldown adds no new information (BUGS.md 2026-09-19).
|
|
101
102
|
* Keyed to the deadline rather than a bare flag so an urgency onset mid-episode — the running
|
|
102
103
|
* build's red verdict arriving after the ordinary warning (BUGS.md 2026-09-30) — is a real
|
|
103
104
|
* transition and warns once more with the earlier deadline. Cleared when the deadline lapses,
|
|
@@ -119,7 +120,7 @@ export class Redeployer {
|
|
|
119
120
|
buildRedSha = null;
|
|
120
121
|
/** Pre-warm during a cooldown (BUGS.md 2026-09-30): the head whose restart the cooldown defers
|
|
121
122
|
* still gets its green check and staged compile — once per SHA — so the lapse reaches the
|
|
122
|
-
* swap directly instead of paying green-check + compile + drain from zero while the
|
|
123
|
+
* swap directly instead of paying green-check + compile + drain from zero while the team sits on
|
|
123
124
|
* a build it already knows is stale. The check and compile are the same side-effect-free effects
|
|
124
125
|
* the episode itself runs (mirror worktree, staging dir under .tumwater/build); what the episode
|
|
125
126
|
* adopts is decided by the adopt helpers, not by the pre-warm. The probes themselves live in
|
|
@@ -136,7 +137,7 @@ export class Redeployer {
|
|
|
136
137
|
* blockedHead: the gate is asked again on every poll, so repairing the environment lets the
|
|
137
138
|
* same head proceed without main moving. Also the dedupe key — one `restart_refused` per
|
|
138
139
|
* distinct reason, not one per poll, and head-independent like the cooldown warning: a
|
|
139
|
-
*
|
|
140
|
+
* merge mid-refusal adds no new information. */
|
|
140
141
|
refusedReason = null;
|
|
141
142
|
/** The live autoRestart flag as last seen by poll — status() publishes the cooldown reason only
|
|
142
143
|
* while it is on (off means no restart will ever be attempted, so a deadline would mislead). */
|
|
@@ -170,26 +171,26 @@ export class Redeployer {
|
|
|
170
171
|
s.checkedHead = this.lastHead;
|
|
171
172
|
// Why a stale build is still the one running. Staleness alone cannot say: a restart that
|
|
172
173
|
// is minutes away and one that was refused hours ago look identical, and on 2026-09-08
|
|
173
|
-
// that gap is what let a blocked restart sit unnoticed while the
|
|
174
|
+
// that gap is what let a blocked restart sit unnoticed while the team ticked on stale
|
|
174
175
|
// code (BUGS.md). Mutually exclusive by construction — block() clears the pending head,
|
|
175
176
|
// and an episode cannot start inside the cooldown, so no two branches can hold at once.
|
|
176
177
|
if (this.pendingHead === this.lastHead)
|
|
177
178
|
s.restartPending = true;
|
|
178
179
|
else if (this.blockedHead === this.lastHead && this.blockedReason)
|
|
179
180
|
s.restartBlocked = this.blockedReason;
|
|
180
|
-
// Inside the post-restart cooldown the
|
|
181
|
+
// Inside the post-restart cooldown the team deliberately keeps ticking on the stale build
|
|
181
182
|
// — say so with a deadline, through the same channel as a refused restart
|
|
182
183
|
// (BUGS.md 2026-09-11).
|
|
183
|
-
// Past it, a startup gate that keeps failing holds the
|
|
184
|
+
// Past it, a startup gate that keeps failing holds the team on the stale build just as
|
|
184
185
|
// surely, and says why the same way (BUGS.md 2026-09-23).
|
|
185
186
|
else if (s.stale && this.autoRestartOn) {
|
|
186
187
|
const { until, urgent } = this.effectiveCooldownUntil(now);
|
|
187
188
|
if (now < until)
|
|
188
189
|
s.restartBlocked =
|
|
189
190
|
`cooldown until ${new Date(until).toISOString()}` +
|
|
190
|
-
(urgent ? ` (the running
|
|
191
|
+
(urgent ? ` (the running version's own commit fails its check — cut to ${RESTART_URGENT_COOLDOWN_MIN} min)` : "");
|
|
191
192
|
else if (this.refusedReason !== null)
|
|
192
|
-
s.restartBlocked = `the
|
|
193
|
+
s.restartBlocked = `the update could not start: ${this.refusedReason}`;
|
|
193
194
|
}
|
|
194
195
|
}
|
|
195
196
|
return s;
|
|
@@ -207,7 +208,7 @@ export class Redeployer {
|
|
|
207
208
|
* why an episode started mid-cooldown. */
|
|
208
209
|
forceRestart() {
|
|
209
210
|
this.forcedRestart = true;
|
|
210
|
-
this.log({
|
|
211
|
+
this.log({ source: TUMWATER_SOURCE, type: "restart_forced", build: this.build.sha });
|
|
211
212
|
}
|
|
212
213
|
/** Read and clear the forced flag — one-shot: the poll that consumes it acts on it. */
|
|
213
214
|
consumeForcedRestart() {
|
|
@@ -218,7 +219,7 @@ export class Redeployer {
|
|
|
218
219
|
}
|
|
219
220
|
/** Start the RUNNING build's own baseline check in the background when the cooldown is
|
|
220
221
|
* deferring a restart and this process has no verdict for the build SHA yet (BUGS.md
|
|
221
|
-
* 2026-09-30). This is the cold-cache recovery the
|
|
222
|
+
* 2026-09-30). This is the cold-cache recovery the team-shared cache cannot give by itself:
|
|
222
223
|
* checkMainBaseline only ever runs at main's tip, so a stale build's SHA gains an entry no
|
|
223
224
|
* other way. The witness check costs at most one suite run per process per build SHA — the
|
|
224
225
|
* cache answers every later consult — and its result can only shorten the wait safely: a
|
|
@@ -260,7 +261,7 @@ export class Redeployer {
|
|
|
260
261
|
const stale = this.staleness?.stale ?? false;
|
|
261
262
|
if (stale && !wasStale) {
|
|
262
263
|
this.log({
|
|
263
|
-
|
|
264
|
+
source: TUMWATER_SOURCE,
|
|
264
265
|
type: "build_stale",
|
|
265
266
|
build: this.build.sha,
|
|
266
267
|
head: mainHead,
|
|
@@ -294,7 +295,7 @@ export class Redeployer {
|
|
|
294
295
|
return this.endDrain();
|
|
295
296
|
// Completed auto-restarts are rate-limited to one per RESTART_COOLDOWN_MS (BUGS.md 2026-09-11):
|
|
296
297
|
// under sustained churn every stale head would otherwise drive a full hold+drain+swap episode
|
|
297
|
-
// back to back. Inside the cooldown the
|
|
298
|
+
// back to back. Inside the cooldown the team keeps ticking on the stale build exactly as when
|
|
298
299
|
// a restart is blocked — no pendingHead, no hold, no drain, no new-tick block — but the
|
|
299
300
|
// deferred head's green check and staged compile still run (prewarm, BUGS.md 2026-09-30), so
|
|
300
301
|
// the lapse reaches the swap directly instead of paying green-check + compile + drain from
|
|
@@ -321,7 +322,7 @@ export class Redeployer {
|
|
|
321
322
|
this.cooldownWarnedUntil = cooldownUntil;
|
|
322
323
|
this.warn(`auto-restart of ${shortSha(mainHead)} deferred — cooldown until ${new Date(cooldownUntil).toISOString()}` +
|
|
323
324
|
(urgent
|
|
324
|
-
? ` (the running
|
|
325
|
+
? ` (the running version ${shortSha(this.build.sha)} fails its check — the cooldown is cut to ${RESTART_URGENT_COOLDOWN_MIN} min)`
|
|
325
326
|
: " (at most one completed restart per 12 h)"));
|
|
326
327
|
}
|
|
327
328
|
this.probes.prewarm(mainHead, this.pendingHead !== null);
|
|
@@ -337,7 +338,7 @@ export class Redeployer {
|
|
|
337
338
|
// per ordinary deadline.
|
|
338
339
|
if (urgent && this.urgentLapseWarnedFor !== this.cooldownUntil()) {
|
|
339
340
|
this.urgentLapseWarnedFor = this.cooldownUntil();
|
|
340
|
-
this.warn(`auto-restart of ${shortSha(mainHead)} proceeding early — the running
|
|
341
|
+
this.warn(`auto-restart of ${shortSha(mainHead)} proceeding early — the running version ${shortSha(this.build.sha)} fails its check, ` +
|
|
341
342
|
`the cooldown was cut to ${RESTART_URGENT_COOLDOWN_MIN} min and that deadline has already passed`);
|
|
342
343
|
}
|
|
343
344
|
if (this.pendingHead === null) {
|
|
@@ -349,7 +350,7 @@ export class Redeployer {
|
|
|
349
350
|
return this.refuse(mainHead, problem, now);
|
|
350
351
|
this.refusedReason = null;
|
|
351
352
|
this.pendingHead = mainHead;
|
|
352
|
-
// Only when the
|
|
353
|
+
// Only when the team was not already being held: a superseded head hands its drain over
|
|
353
354
|
// to the new one rather than starting a fresh window (see drainSince).
|
|
354
355
|
if (!this.drainSince) {
|
|
355
356
|
this.drainSince = now;
|
|
@@ -362,6 +363,10 @@ export class Redeployer {
|
|
|
362
363
|
// lapse reaches the swap directly instead of paying green-check + compile from zero.
|
|
363
364
|
this.green = this.probes.adoptGreen(mainHead);
|
|
364
365
|
this.compiled = this.probes.adoptCompile(mainHead);
|
|
366
|
+
// An adopted compile skips the `!this.compiled` branch below, the only other place the
|
|
367
|
+
// hold is announced, so say it here or the episode logs nothing until `restart`.
|
|
368
|
+
if (this.compiled)
|
|
369
|
+
this.logRestartPending(mainHead);
|
|
365
370
|
return "hold";
|
|
366
371
|
}
|
|
367
372
|
if (!this.green?.done)
|
|
@@ -369,7 +374,7 @@ export class Redeployer {
|
|
|
369
374
|
// A REJECTED check is not a verdict: it could not run (git broke inside the mirror worktree,
|
|
370
375
|
// or the check itself threw), so it says nothing about the tree. Drop the pending head — no
|
|
371
376
|
// blockedHead, no latched "main is red" — warn once per episode, and let the next poll
|
|
372
|
-
// re-run the check on the same head; a
|
|
377
|
+
// re-run the check on the same head; a team whose toolchain recovers redeploys itself
|
|
373
378
|
// without main ever moving (BUGS.md 2026-09-16). A red VERDICT below still blocks.
|
|
374
379
|
if (this.green.error) {
|
|
375
380
|
this.noteFailure(now);
|
|
@@ -387,19 +392,7 @@ export class Redeployer {
|
|
|
387
392
|
}
|
|
388
393
|
if (!this.compiled) {
|
|
389
394
|
this.compiled = this.probes.adoptCompile(mainHead) ?? track(this.deps.compile(mainHead));
|
|
390
|
-
|
|
391
|
-
// poll starts a fresh one, and a state stream that re-enters "compiling" it never left
|
|
392
|
-
// is noise on top of the missing terminal event (BUGS.md 2026-09-28).
|
|
393
|
-
if (this.pendingLoggedHead !== mainHead) {
|
|
394
|
-
this.pendingLoggedHead = mainHead;
|
|
395
|
-
this.log({
|
|
396
|
-
loop: "harness",
|
|
397
|
-
type: "restart_pending",
|
|
398
|
-
build: this.build.sha,
|
|
399
|
-
head: mainHead,
|
|
400
|
-
aheadCommits: this.staleness.aheadCommits,
|
|
401
|
-
});
|
|
402
|
-
}
|
|
395
|
+
this.logRestartPending(mainHead);
|
|
403
396
|
return "hold";
|
|
404
397
|
}
|
|
405
398
|
if (!this.compiled.done)
|
|
@@ -422,26 +415,26 @@ export class Redeployer {
|
|
|
422
415
|
if (this.compileFailedHead !== mainHead) {
|
|
423
416
|
this.compileFailedHead = mainHead;
|
|
424
417
|
this.warn(c === undefined
|
|
425
|
-
? `
|
|
426
|
-
: `could not start
|
|
418
|
+
? `the update to ${shortSha(mainHead)} could not be built: ${detail} — retrying on the next poll`
|
|
419
|
+
: `the update could not start: ${detail} — retrying on the next poll`);
|
|
427
420
|
}
|
|
428
421
|
this.clearPending();
|
|
429
422
|
return this.endDrain();
|
|
430
423
|
}
|
|
431
|
-
const reason = `
|
|
432
|
-
this.block(mainHead, reason, `${reason} — staying on
|
|
424
|
+
const reason = `the update to ${shortSha(mainHead)} failed to build`;
|
|
425
|
+
this.block(mainHead, reason, `${reason} — staying on version ${shortSha(this.build.sha)}: ${detail}`);
|
|
433
426
|
return this.endDrain();
|
|
434
427
|
}
|
|
435
428
|
// The drain rule, split by who requested the work (BUGS.md 2026-09-08): a director tick is
|
|
436
429
|
// an explicit human prompt and outranks the redeploy — while one is in flight the hold has no
|
|
437
430
|
// time cap: no swap and no abort until it finishes. The per-tick watchdogs already bound how
|
|
438
|
-
// long one run can take, so this cannot hang the
|
|
431
|
+
// long one run can take, so this cannot hang the team beyond what a single tick can do.
|
|
439
432
|
if (inFlight.directorInFlight > 0)
|
|
440
433
|
return "hold";
|
|
441
434
|
if (inFlight.roleInFlight > 0 && now - this.drainSince < this.drainWindowMs)
|
|
442
435
|
return "hold";
|
|
443
436
|
// Ask the startup gate once more, right before the point of no return: the environment can
|
|
444
|
-
// change during a long drain — a
|
|
437
|
+
// change during a long drain — a merge already in flight when the hold began can still
|
|
445
438
|
// delete tumwater.json (the 2026-09-22 incident's own cause). Refused, the episode is
|
|
446
439
|
// dropped rather than latched, so the next poll starts over at the gate above: nothing is
|
|
447
440
|
// held while it keeps failing, and a fixed environment gets a full episode with its own drain.
|
|
@@ -455,7 +448,7 @@ export class Redeployer {
|
|
|
455
448
|
}
|
|
456
449
|
catch (err) {
|
|
457
450
|
this.noteFailure(now);
|
|
458
|
-
const reason = "swapping the
|
|
451
|
+
const reason = "swapping the update into place failed";
|
|
459
452
|
this.block(mainHead, reason, `${reason}: ${errorMessage(err)}`);
|
|
460
453
|
return this.endDrain();
|
|
461
454
|
}
|
|
@@ -477,27 +470,42 @@ export class Redeployer {
|
|
|
477
470
|
// The director is guaranteed finished by here (poll only reaches the swap with
|
|
478
471
|
// directorInFlight === 0), so what gets aborted — and counted — are role ticks only, and
|
|
479
472
|
// only those holding a permit (see InFlightCounts). A director-extended hold reports
|
|
480
|
-
//
|
|
473
|
+
// finishedMs past the window: correct and informative.
|
|
481
474
|
this.log({
|
|
482
|
-
|
|
475
|
+
source: TUMWATER_SOURCE,
|
|
483
476
|
type: "restart",
|
|
484
477
|
from: this.build.sha,
|
|
485
478
|
to: mainHead,
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
479
|
+
finishedMs: now - this.drainSince,
|
|
480
|
+
abortedRuns: inFlight.roleInFlight,
|
|
481
|
+
finishWindowMs: this.drainWindowMs,
|
|
489
482
|
});
|
|
490
483
|
// The state stream left "compiling"; a later episode for this same head is a new transition.
|
|
491
484
|
this.pendingLoggedHead = null;
|
|
492
485
|
return "restart";
|
|
493
486
|
}
|
|
494
|
-
/** Nothing is being waited for any more — the
|
|
487
|
+
/** Nothing is being waited for any more — the team schedules normally again, so whatever
|
|
495
488
|
* drain was running is over and the next one starts its clock from scratch. Every path out of
|
|
496
489
|
* poll that is not a `hold` goes through here. */
|
|
497
490
|
endDrain() {
|
|
498
491
|
this.drainSince = 0;
|
|
499
492
|
return "none";
|
|
500
493
|
}
|
|
494
|
+
// Once per head, not once per episode: a rejected compile drops the episode and the next
|
|
495
|
+
// poll starts a fresh one, and a state stream that re-enters "compiling" it never left
|
|
496
|
+
// is noise on top of the missing terminal event (BUGS.md 2026-09-28).
|
|
497
|
+
logRestartPending(mainHead) {
|
|
498
|
+
if (this.pendingLoggedHead === mainHead)
|
|
499
|
+
return;
|
|
500
|
+
this.pendingLoggedHead = mainHead;
|
|
501
|
+
this.log({
|
|
502
|
+
source: TUMWATER_SOURCE,
|
|
503
|
+
type: "restart_pending",
|
|
504
|
+
build: this.build.sha,
|
|
505
|
+
head: mainHead,
|
|
506
|
+
aheadCommits: this.staleness?.aheadCommits ?? 0,
|
|
507
|
+
});
|
|
508
|
+
}
|
|
501
509
|
/** deps.bootProblem, fail-closed: a gate that throws cannot vouch for the successor, so its
|
|
502
510
|
* error is the refusal's reason (and, like any refusal, it is asked again next poll). */
|
|
503
511
|
async bootProblem() {
|
|
@@ -514,7 +522,7 @@ export class Redeployer {
|
|
|
514
522
|
this.noteFailure(now);
|
|
515
523
|
if (reason !== this.refusedReason) {
|
|
516
524
|
this.refusedReason = reason;
|
|
517
|
-
this.log({
|
|
525
|
+
this.log({ source: TUMWATER_SOURCE, type: "restart_refused", from: this.build.sha, to: head, reason });
|
|
518
526
|
}
|
|
519
527
|
// The state stream left "compiling" via the refusal event; a later episode for the same
|
|
520
528
|
// head — the gate re-asked after the environment was repaired — is a new transition.
|
|
@@ -532,12 +540,12 @@ export class Redeployer {
|
|
|
532
540
|
this.blockedHead = head;
|
|
533
541
|
this.blockedReason = reason;
|
|
534
542
|
this.clearPending();
|
|
535
|
-
// A typed transition, not only a warning: the digest's
|
|
543
|
+
// A typed transition, not only a warning: the digest's Team state changes section replays
|
|
536
544
|
// the decisions, and a stream that entered "compiling" must leave it when the compile dies
|
|
537
545
|
// — its two other endings (restart, restart_refused) each have an event, so the third does
|
|
538
546
|
// too (BUGS.md 2026-09-28). Logged before the warning, so the warning is never the state
|
|
539
547
|
// stream's only trace of the episode's end.
|
|
540
|
-
this.log({
|
|
548
|
+
this.log({ source: TUMWATER_SOURCE, type: "restart_blocked", from: this.build.sha, to: head, reason });
|
|
541
549
|
this.warn(message);
|
|
542
550
|
}
|
|
543
551
|
/** A restart attempt just failed — record when, so sustained staleness WITH failures can
|
|
@@ -551,7 +559,7 @@ export class Redeployer {
|
|
|
551
559
|
/** The sustained-pin warning (BUGS.md 2026-09-29): once the build has stayed stale past
|
|
552
560
|
* STALE_ESCALATE_AFTER_MS and a restart attempt has failed within this same stale episode,
|
|
553
561
|
* say the aggregate out loud — at most once per STALE_ESCALATE_EVERY_MS while it lasts.
|
|
554
|
-
* Healthy churn never feeds it: a
|
|
562
|
+
* Healthy churn never feeds it: a merge restart clears the episode, and a cooldown
|
|
555
563
|
* deferral or red main produces no failed attempt, so no warning. */
|
|
556
564
|
escalateIfSustained(now) {
|
|
557
565
|
const since = this.staleSince;
|
|
@@ -563,10 +571,10 @@ export class Redeployer {
|
|
|
563
571
|
return;
|
|
564
572
|
this.lastEscalationAt = now;
|
|
565
573
|
const hours = Math.max(1, Math.round((now - since) / 3_600_000));
|
|
566
|
-
this.warn(`
|
|
574
|
+
this.warn(`an update has waited ~${hours} h to install (running ${shortSha(this.build.sha)}, ${this.staleness?.aheadCommits ?? 0} commits behind main) while update attempts keep failing — the stuck version itself is the problem, not any one commit`);
|
|
567
575
|
}
|
|
568
576
|
/** Log one warning event for the harness loop — the shape the class's warn sites share. */
|
|
569
577
|
warn(message) {
|
|
570
|
-
this.log({
|
|
578
|
+
this.log({ source: TUMWATER_SOURCE, type: "warning", message });
|
|
571
579
|
}
|
|
572
580
|
}
|
|
@@ -8,7 +8,7 @@ import { startParentDeathWatch } from "../process/supervisor.js";
|
|
|
8
8
|
*
|
|
9
9
|
* Unlike the orchestrator — which the supervisor respawns onto a fresh build — a dashboard is
|
|
10
10
|
* started by hand and, without this, keeps serving its startup code until the operator
|
|
11
|
-
* restarts it. When the
|
|
11
|
+
* restarts it. When the team redeploys itself (redeploy.ts swaps `dist/`) the TUI keeps
|
|
12
12
|
* rendering old modules and the GUI keeps serving old page JS. This module watches for a
|
|
13
13
|
* newer compiled tree on disk and hands itself to a fresh copy of the same command (a supervised
|
|
14
14
|
* respawn, not a nested wrapper), so both surfaces come back on the new code within a poll.
|