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,4 @@
|
|
|
1
|
-
/** The daily-cost budget: a loop's and the
|
|
1
|
+
/** The daily-cost budget: a loop's and the team's local-day spend and the gate those figures
|
|
2
2
|
* drive (plans/daily-cost-budget.md, plans/fallback-model.md). The circuit breaker that judges
|
|
3
3
|
* whether the gate's fallback can actually serve lives beside it in fallback-breaker.ts. Split
|
|
4
4
|
* out of loop-state.ts, which owns
|
|
@@ -17,7 +17,7 @@ export function todayStamp(now = Date.now()) {
|
|
|
17
17
|
}
|
|
18
18
|
/** A spend figure is finite and non-negative (json-object.ts's nonNegativeNumber); a string,
|
|
19
19
|
* NaN, ±Infinity, or a negative hand-edited value reads as $0. Negative spend is as poisonous
|
|
20
|
-
* to the cap as NaN: it makes every `>= cap` comparison false — and drags the
|
|
20
|
+
* to the cap as NaN: it makes every `>= cap` comparison false — and drags the team sum down —
|
|
21
21
|
* silently defeating the budget's safety valve. */
|
|
22
22
|
function spendNumber(value) {
|
|
23
23
|
return nonNegativeNumber(value, 0);
|
|
@@ -28,7 +28,7 @@ function spendNumber(value) {
|
|
|
28
28
|
* non-finite, or negative value reads as $0 too: `loadLoopState` heals the field on read, but
|
|
29
29
|
* this is the budget gate's own guard, and a hand-edited or torn `dayCostUsd` (or an in-memory
|
|
30
30
|
* state) would otherwise concatenate into
|
|
31
|
-
*
|
|
31
|
+
* teamDailyCost's sum (or make every `>= cap` comparison false) and silently defeat the cap,
|
|
32
32
|
* the budget's safety valve. Reads never mutate. See plans/daily-cost-budget.md. */
|
|
33
33
|
export function dailyCost(s, now = Date.now()) {
|
|
34
34
|
return s.dayStamp === todayStamp(now) ? spendNumber(s.dayCostUsd) : 0;
|
|
@@ -40,16 +40,33 @@ export function dailyCost(s, now = Date.now()) {
|
|
|
40
40
|
* authoritative across an in-flight tick; the value persists at tick end with the rest of the
|
|
41
41
|
* state. Both operands are guarded through spendNumber so a corrupt stored value or a
|
|
42
42
|
* non-finite/negative run cost cannot poison the window with a string/NaN/negative sum. */
|
|
43
|
-
export function recordDailyCost(s, usd, now = Date.now()) {
|
|
43
|
+
export function recordDailyCost(s, usd, now = Date.now(), pair) {
|
|
44
44
|
const stamp = todayStamp(now);
|
|
45
45
|
if (s.dayStamp !== stamp) {
|
|
46
46
|
s.dayStamp = stamp;
|
|
47
47
|
s.dayCostUsd = 0;
|
|
48
|
+
s.dayCostByPair = {};
|
|
48
49
|
}
|
|
49
50
|
s.dayCostUsd = spendNumber(s.dayCostUsd) + spendNumber(usd);
|
|
51
|
+
// The per-step split (a priced fallback step's own cap): folded under the run's pair name,
|
|
52
|
+
// an own property so a `__proto__` pair cannot reach the prototype.
|
|
53
|
+
if (pair !== undefined) {
|
|
54
|
+
const by = (s.dayCostByPair ??= {});
|
|
55
|
+
const sum = spendNumber(Object.hasOwn(by, pair) ? by[pair] : 0) + spendNumber(usd);
|
|
56
|
+
Object.defineProperty(by, pair, { value: sum, enumerable: true, writable: true, configurable: true });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** One model pair's spend for the local day, summed over every loop's per-pair window
|
|
60
|
+
* (`dayCostByPair`): $0 for a stale day stamp or a pair no run has charged. */
|
|
61
|
+
export function pairDailyCost(states, pair, now = Date.now()) {
|
|
62
|
+
const today = todayStamp(now);
|
|
63
|
+
return states.reduce((sum, s) => {
|
|
64
|
+
const by = s.dayStamp === today ? s.dayCostByPair : undefined;
|
|
65
|
+
return sum + (by !== undefined && Object.hasOwn(by, pair) ? spendNumber(by[pair]) : 0);
|
|
66
|
+
}, 0);
|
|
50
67
|
}
|
|
51
|
-
/** The
|
|
52
|
-
export function
|
|
68
|
+
/** The team's spend for the local day: every loop's daily window summed. */
|
|
69
|
+
export function teamDailyCost(states, now = Date.now()) {
|
|
53
70
|
return states.reduce((sum, s) => sum + dailyCost(s, now), 0);
|
|
54
71
|
}
|
|
55
72
|
/** True when today's spend has reached the daily cost budget — the view is non-null, its cap is
|
|
@@ -57,23 +74,23 @@ export function fleetDailyCost(states, now = Date.now()) {
|
|
|
57
74
|
* above it. The single definition of "the budget gate is on": the orchestrator evaluates it from
|
|
58
75
|
* live loop states via budgetPaused, and both dashboards evaluate it from the snapshot's
|
|
59
76
|
* materialized budget field (status/status-data.ts) — which carries the object even while
|
|
60
|
-
* disabled, so the capUsd > 0 term is what keeps a disabled
|
|
77
|
+
* disabled, so the capUsd > 0 term is what keeps a disabled team out of "budget paused". The
|
|
61
78
|
* comparison cannot drift between what the scheduler enforces and what users see. */
|
|
62
79
|
export function budgetReached(budget) {
|
|
63
80
|
return budget !== null && budget.capUsd > 0 && budget.spentUsd >= budget.capUsd;
|
|
64
81
|
}
|
|
65
|
-
/** The fixed fraction of the daily cap at which the
|
|
82
|
+
/** The fixed fraction of the daily cap at which the team pages the operator that spend is
|
|
66
83
|
* closing in on it (PLANS.md 2026-09-30): opinionated default, no config knob. */
|
|
67
84
|
export const BUDGET_WARNING_FRACTION = 0.8;
|
|
68
85
|
/** True when today's spend has crossed `BUDGET_WARNING_FRACTION` of the daily cap while the
|
|
69
86
|
* budget gate is still open — the early heads-up beside budgetReached, which fires only once
|
|
70
|
-
* the cap itself is reached and the
|
|
87
|
+
* the cap itself is reached and the team has already stopped (or demoted) its ticks. Same
|
|
71
88
|
* guard terms as budgetReached (non-null view, a cap of 0 disables the budget), scaled by the
|
|
72
89
|
* warning fraction, so the two views can never disagree about whether the budget is on. */
|
|
73
90
|
export function budgetWarning(budget) {
|
|
74
91
|
return (budget !== null && budget.capUsd > 0 && budget.spentUsd >= budget.capUsd * BUDGET_WARNING_FRACTION);
|
|
75
92
|
}
|
|
76
|
-
/** True while the
|
|
93
|
+
/** True while the team's spend for the local day has reached `maxDailyCostUsd` (a cap of 0
|
|
77
94
|
* disables the budget). The orchestrator re-evaluates this every poll from its runners' live
|
|
78
95
|
* states and the freshly reloaded config — resume is stateless, so raising/disabling the cap
|
|
79
96
|
* or crossing midnight flips it on the next cycle and nothing can get stuck. Lives here (not
|
|
@@ -81,13 +98,13 @@ export function budgetWarning(budget) {
|
|
|
81
98
|
export function budgetPaused(states, config, now = Date.now()) {
|
|
82
99
|
return budgetReached(budgetSpend(states, config, now));
|
|
83
100
|
}
|
|
84
|
-
/** The
|
|
101
|
+
/** The team's daily-budget figures from live loop states — the exact pair the gate evaluates:
|
|
85
102
|
* today's summed spend and the configured cap (0 when disabled). budgetReached's own capUsd > 0
|
|
86
103
|
* term makes a disabled cap read unreached, so the pair needs no null ceremony. Returning the
|
|
87
104
|
* figures (not just the verdict) lets the scheduler publish WHAT it tripped on, not only THAT it
|
|
88
105
|
* tripped — the one number every observer must show. */
|
|
89
106
|
export function budgetSpend(states, config, now = Date.now()) {
|
|
90
|
-
return { spentUsd:
|
|
107
|
+
return { spentUsd: teamDailyCost(states, now), capUsd: config.maxDailyCostUsd };
|
|
91
108
|
}
|
|
92
109
|
/** When today's burn will reach the cap, as a local-day epoch-ms instant — the budget badge's
|
|
93
110
|
* `~cap at HH:MM` forecast — or null when no forecast may be stated: no cap (`capUsd <= 0`, the
|
|
@@ -123,8 +140,8 @@ export function modelPairName(p) {
|
|
|
123
140
|
* (budgetReached for observers, budgetPaused for the scheduler), and whether the `default` and
|
|
124
141
|
* `strong` tiers resolved to a usable free pair (resolveTierFallbacks over the full usable
|
|
125
142
|
* predicate — price AND breaker serving) — plus whether the review gate is on. The gate is
|
|
126
|
-
* `paused` when `default` resolves to pause (nothing for the
|
|
127
|
-
* review is on and `strong` does (nothing could
|
|
143
|
+
* `paused` when `default` resolves to pause (nothing for the team's bulk to run on), or when
|
|
144
|
+
* review is on and `strong` does (nothing could merge, so every authoring tick is wasted work);
|
|
128
145
|
* `fallback` otherwise, with the tiers that did resolve running their pairs and the rest held
|
|
129
146
|
* per role. The single definition of the gate: the orchestrator enforces it and both dashboards
|
|
130
147
|
* display it, so what an operator sees is what the scheduler is doing. */
|
|
@@ -140,7 +157,7 @@ export function budgetGate(reached, tiers, reviewOn) {
|
|
|
140
157
|
/** The configured fallback tier map as display entries: one `tier: pair` per set tier, in
|
|
141
158
|
* MODEL_TIERS order (small, default, strong). The single home of the tier-list rendering
|
|
142
159
|
* shared by the budget badge (src/ui/badges.ts), the fallback alert's detail
|
|
143
|
-
* (src/ui/
|
|
160
|
+
* (src/ui/team-alerts.ts), and the budget_fallback event line
|
|
144
161
|
* (src/events/event-format.ts), so the pair shape and the tier order cannot drift between
|
|
145
162
|
* the three surfaces; each caller joins or length-tests the entries itself. A non-string
|
|
146
163
|
* value is not a pair (the event reads a loosely typed field) and is left out; absent or
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
1
2
|
/** The fallback's circuit breaker (BUGS.md 2026-09-20): whether the engaged free fallback can
|
|
2
3
|
* actually SERVE. Split out of budget.ts, which keeps the daily-cost figures and the gate they
|
|
3
4
|
* drive; the breaker is a self-contained pure state machine — no scheduler, no pi model
|
|
@@ -5,13 +6,13 @@
|
|
|
5
6
|
* orchestrator can hold it in memory and fold tick outcomes into it without importing the
|
|
6
7
|
* budget. The gate reads its verdict through fallbackServing (budget.ts's budgetGate). */
|
|
7
8
|
/** failureLimit 3 is a single loop's failing-streak threshold (tick-apply.ts ERROR_STREAK_WARN)
|
|
8
|
-
* applied
|
|
9
|
+
* applied team-wide: consecutive across every role, so any tick that served in between resets
|
|
9
10
|
* it — a healthy backend with one flaky tick never trips it, while a dead one trips on its
|
|
10
11
|
* first three ticks (on 2026-09-19 the third failure landed 24 minutes into the hour; the 30
|
|
11
12
|
* ticks after it were the waste). A 5-minute first cool-down because a failed probe costs one
|
|
12
13
|
* tick — about half of that hour's failures came back in under 10 s, the rest ran minutes
|
|
13
14
|
* before the 400 — which is cheap next to idling until midnight; doubling to at most 30 minutes
|
|
14
|
-
* bounds how long an operator who fixed the backend waits for the
|
|
15
|
+
* bounds how long an operator who fixed the backend waits for the team to notice (editing the
|
|
15
16
|
* cap or the pair re-trusts it at once — see rekeyFallbackBreaker). */
|
|
16
17
|
export const FALLBACK_BREAKER_POLICY = {
|
|
17
18
|
failureLimit: 3,
|
|
@@ -64,14 +65,14 @@ export function abandonFallbackProbe(b) {
|
|
|
64
65
|
* alone: every way a backend cannot serve — an HTTP 4xx/5xx like oMLX's prefill guard, a
|
|
65
66
|
* refused connection, a request that never returns (the tick timeout) — ends a tick as `error`,
|
|
66
67
|
* and 33 of 33 did on 2026-09-19. (An `error` can also be local, e.g. a worktree that will not
|
|
67
|
-
* reset; failureLimit of those in a row
|
|
68
|
+
* reset; failureLimit of those in a row team-wide is still a team that cannot work, and the
|
|
68
69
|
* probe wins the pause back.) `served` is a result only a model reply produces: a change bound
|
|
69
|
-
* for
|
|
70
|
+
* for merge (`queued`, `changed`), an explicit decision (`no_change`, `refused`), or a
|
|
70
71
|
* reviewer's rejection. Everything else is no evidence: harness and operator kills (`aborted`,
|
|
71
72
|
* `user_aborted`), ticks that never reached the model (`skipped`, `main_red`), a quiet kill
|
|
72
73
|
* (the model had answered before the stall — a tool call or the stream stopped), and the
|
|
73
|
-
*
|
|
74
|
-
* returns them since leftover recovery queues its pin instead of
|
|
74
|
+
* merge results (`merge_conflict`, `merge_blocked`, `review_error`) — a tick no longer
|
|
75
|
+
* returns them since leftover recovery queues its pin instead of merging it, and they would
|
|
75
76
|
* not say whether the backend was involved. */
|
|
76
77
|
export function fallbackEvidence(result) {
|
|
77
78
|
switch (result) {
|
|
@@ -114,7 +115,7 @@ export function recordFallbackTick(b, ranOn, result, probe, now, policy = FALLBA
|
|
|
114
115
|
return { ...b, failures, probeAt: now + policy.cooldownMs, cooldownMs: policy.cooldownMs, probing: false };
|
|
115
116
|
}
|
|
116
117
|
/** What orchestrator.json publishes while the breaker holds the fallback demoted
|
|
117
|
-
* (
|
|
118
|
+
* (team/orchestrator-info.ts's OrchestratorInfo.fallbackDemoted). Observers need it because the
|
|
118
119
|
* price check alone would advertise the demoted pair as a working fallback —
|
|
119
120
|
* status/status-data.ts nulls the snapshot's `fallback` from it and `tumwater doctor` names it.
|
|
120
121
|
* Undefined while the fallback is trusted or not engaged. */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
1
2
|
import fs from "node:fs";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { isJsonObject } from "../files/json-object.js";
|
|
@@ -79,7 +80,7 @@ function isCheckScript(s, key) {
|
|
|
79
80
|
export function detectBuildCheck(startDir, config, maxLevels = WALK_UP_LEVELS) {
|
|
80
81
|
const c = config?.check;
|
|
81
82
|
// check.timeoutSeconds bounds the npm walk-up's check too: it is how a repo whose suite
|
|
82
|
-
// outgrows 300 s under load says so (the
|
|
83
|
+
// outgrows 300 s under load says so (the team's own gate runs were timing out there).
|
|
83
84
|
const configuredMs = typeof c?.timeoutSeconds === "number" && c.timeoutSeconds > 0 ? c.timeoutSeconds * 1000 : undefined;
|
|
84
85
|
if (c && isNonBlankString(c.command)) {
|
|
85
86
|
return {
|
|
@@ -95,10 +96,10 @@ export function detectBuildCheck(startDir, config, maxLevels = WALK_UP_LEVELS) {
|
|
|
95
96
|
}
|
|
96
97
|
/** The configured per-change gate command (`check.gateCommand`, PLANS.md Land-queue speed 3e) when
|
|
97
98
|
* it is a non-blank string, else undefined — off. Opt-in: a repo that sets it trades a cheaper
|
|
98
|
-
* review-gate pre-check for a full check.command run that happens once per stack at the
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
99
|
+
* review-gate pre-check for a full check.command run that happens once per stack at the batch/merge
|
|
100
|
+
* scope instead. One reading shared by runScopedBuildCheck (which runs it at scope "gate") and
|
|
101
|
+
* merge-to-main.ts's verifyMerge (which must then never treat the gate's green as the full
|
|
102
|
+
* check's), so the two cannot disagree about whether it is on. */
|
|
102
103
|
export function gateCommandOf(config) {
|
|
103
104
|
const g = config?.check?.gateCommand;
|
|
104
105
|
return isNonBlankString(g) ? g : undefined;
|
|
@@ -108,9 +109,9 @@ export function gateCommandOf(config) {
|
|
|
108
109
|
* of its own (node_modules is gitignored, so it exists only where someone ran npm install), and
|
|
109
110
|
* neither does a project nested under an installed root. Returns the first existing path, or
|
|
110
111
|
* null when no ancestor within `maxLevels` has it. Assuming a local install instead is what
|
|
111
|
-
* broke the
|
|
112
|
+
* broke the team's own redeploy on 2026-09-08: compileStaged looked only at
|
|
112
113
|
* `<root>/node_modules/typescript`, so its test could not pass in a bare worktree, the failing
|
|
113
|
-
* suite made main read red, and the blocked restart stranded the
|
|
114
|
+
* suite made main read red, and the blocked restart stranded the team on a stale build
|
|
114
115
|
* (BUGS.md). Never throws. */
|
|
115
116
|
export function resolveFromNodeModules(startDir, rel, maxLevels = WALK_UP_LEVELS) {
|
|
116
117
|
return walkUp(startDir, maxLevels, (dir) => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
1
2
|
/** The presentation half of the build check: the scope vocabulary, the environmental-skip
|
|
2
3
|
* warning's wording, and the build_check event's shape. Split out of build-check.ts — which
|
|
3
4
|
* keeps the deterministic execution and classification (detect → spawn → classify) — because
|
|
@@ -7,6 +8,30 @@
|
|
|
7
8
|
* running a check. This module imports only types from build-check.js, so no runtime cycle.
|
|
8
9
|
* runScopedBuildCheck (build-check-scoped.ts) is the only caller that both runs a check and
|
|
9
10
|
* uses this wording; the baseline surfaces use the event/warning helpers alone. */
|
|
11
|
+
/** Does this build_check scope stand for a full-suite verdict — merge, batch, or the red-main
|
|
12
|
+
* baseline? A gate check can run a subset of the tree, so a scope outside this set is never a
|
|
13
|
+
* full-suite check. The one vocabulary the three predicates that recognize a full-suite check
|
|
14
|
+
* select on — isPassingFullCheck, isRedFullCheck, and the status polls' newest-merge-check
|
|
15
|
+
* scan (status-polls.ts) — so they cannot drift on which scopes count. The deadline-killed
|
|
16
|
+
* failure costing in time-spend.ts is a narrower pair (merge/batch only, with a status and a
|
|
17
|
+
* timeout required), so it keeps its own check. */
|
|
18
|
+
export function isFullCheckScope(scope) {
|
|
19
|
+
return scope === "merge" || scope === "batch" || scope === "baseline";
|
|
20
|
+
}
|
|
21
|
+
/** Is this a passing build_check over the full suite? The scopes doctor's check row counts —
|
|
22
|
+
* merge, batch and the red-main baseline — are isFullCheckScope's set; a gate check can run a
|
|
23
|
+
* subset, so it is left out. */
|
|
24
|
+
export function isPassingFullCheck(ev) {
|
|
25
|
+
return ev.type === "build_check" && ev.status === "passed" && isFullCheckScope(ev.scope);
|
|
26
|
+
}
|
|
27
|
+
/** The red sibling of isPassingFullCheck: a failed build_check over the same three scopes
|
|
28
|
+
* (isFullCheckScope's set). A `skipped` check made no verdict about the tree, and a merge-scope
|
|
29
|
+
* timeout is already remapped to `failed` by runScopedBuildCheck (`MERGE_SCOPES`), so `failed`
|
|
30
|
+
* alone is the rule. The two predicates are mutually exclusive on `status`, so a fold branch on
|
|
31
|
+
* them never sees the other's events. */
|
|
32
|
+
export function isRedFullCheck(ev) {
|
|
33
|
+
return ev.type === "build_check" && ev.status === "failed" && isFullCheckScope(ev.scope);
|
|
34
|
+
}
|
|
10
35
|
/** How late a check's deadline timer may fire before the timeout's wording stops naming the
|
|
11
36
|
* configured bound alone. Normal timer lag is milliseconds; past this the warning and the
|
|
12
37
|
* merge-scope reason name the wall-clock time the deadline actually fired and how late (see
|
|
@@ -25,7 +50,7 @@ export const SLEEP_SPAN_TOLERANCE_MS = 5_000;
|
|
|
25
50
|
* says nothing about the tree: a skip from a signal the harness did not send, a timeout whose
|
|
26
51
|
* deadline demonstrably fired late, and a failure the host slept through. The one rule for every
|
|
27
52
|
* scope — runScopedBuildCheck and the red-main baseline (checkMainBaseline) both ask it. */
|
|
28
|
-
|
|
53
|
+
function owesCleanRetry(first) {
|
|
29
54
|
if (first.status === "failed")
|
|
30
55
|
return (first.run?.sleptMs ?? 0) > SLEEP_SPAN_TOLERANCE_MS;
|
|
31
56
|
if (first.status !== "skipped")
|
|
@@ -33,22 +58,43 @@ export function owesCleanRetry(first) {
|
|
|
33
58
|
return (first.skipReason === "killed" ||
|
|
34
59
|
(first.skipReason === "timeout" && (first.run?.deadlineLateMs ?? 0) > DEADLINE_LATE_TOLERANCE_MS));
|
|
35
60
|
}
|
|
61
|
+
/** Run one build-check attempt, priced, and give a verdict-less first attempt one clean retry
|
|
62
|
+
* — the timing-and-counting discipline shared by runScopedBuildCheck (build-check-scoped.ts)
|
|
63
|
+
* and the red-main baseline (checkMainBaseline, main-baseline.ts), so the two surfaces cannot
|
|
64
|
+
* drift on how long one check "took" or on how many attempts a weather-killed run owes
|
|
65
|
+
* (BUGS.md 2026-09-30). `run` starts one attempt; the returned `durationMs` prices the attempt
|
|
66
|
+
* whose verdict stands — the first, unless owesCleanRetry, in which case the retry's. `onFirst`
|
|
67
|
+
* prices the verdict-less first attempt the way the caller's surface reports runs
|
|
68
|
+
* (runScopedBuildCheck logs a build_check event; checkMainBaseline feeds its onRun hook), so a
|
|
69
|
+
* retried attempt's cost is still recorded even though its verdict is discarded.` */
|
|
70
|
+
export async function runWithCleanRetry(run, onFirst) {
|
|
71
|
+
const startedAt = Date.now();
|
|
72
|
+
const first = await run();
|
|
73
|
+
let durationMs = Date.now() - startedAt;
|
|
74
|
+
if (!owesCleanRetry(first))
|
|
75
|
+
return { outcome: first, durationMs };
|
|
76
|
+
onFirst?.(first, durationMs);
|
|
77
|
+
const retryStart = Date.now();
|
|
78
|
+
const retry = await run();
|
|
79
|
+
durationMs = Date.now() - retryStart;
|
|
80
|
+
return { outcome: retry, durationMs };
|
|
81
|
+
}
|
|
36
82
|
/** Per-scope wording for the environmental-skip warning. The call sites' current messages
|
|
37
83
|
* are identical apart from these words, so keying them on the scope keeps each surface's feed
|
|
38
84
|
* line byte-for-byte what it is today. */
|
|
39
85
|
export const SCOPE_WORDS = {
|
|
40
|
-
gate: { label: "
|
|
41
|
-
|
|
42
|
-
// The batch's next step after the check is the fast-forward — the same phrase the
|
|
43
|
-
//
|
|
44
|
-
batch: { label: "batch
|
|
86
|
+
gate: { label: "check", proceeding: "proceeding to model review" },
|
|
87
|
+
merge: { label: "merge check", proceeding: "proceeding to merge" },
|
|
88
|
+
// The batch's next step after the check is the fast-forward — the same phrase the merge scope
|
|
89
|
+
// uses (gate says "proceeding to model review" because its next step is the reviewer).
|
|
90
|
+
batch: { label: "batch check", proceeding: "proceeding to merge" },
|
|
45
91
|
};
|
|
46
|
-
/** Scopes whose outcome gates a merge to main: runScopedBuildCheck remaps a timeout or an
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
export const MERGE_SCOPES = new Set(["
|
|
92
|
+
/** Scopes whose outcome gates a merge to main: runScopedBuildCheck remaps a timeout or an external
|
|
93
|
+
* signal kill at these scopes to a deterministic "failed" — the tree is unverified, and these are
|
|
94
|
+
* the last checks before main. The gate scope's pre-check stays fail-open because the model
|
|
95
|
+
* reviewer and the merge path's own check still stand behind it (BUGS.md: a merge build check that
|
|
96
|
+
* times out must not merge unverified). */
|
|
97
|
+
export const MERGE_SCOPES = new Set(["merge", "batch"]);
|
|
52
98
|
/** The proceeding reason a merge-scope check that made no verdict about the tree carries: the
|
|
53
99
|
* run was killed, timed out, or spanned a host sleep, so nothing it saw is the change's
|
|
54
100
|
* failure. The single home of that wording, shared by runScopedBuildCheck's four merge-scope
|
|
@@ -79,7 +125,7 @@ export function timedOutPhrase(timeoutMs, run) {
|
|
|
79
125
|
// swap-thrashing host, a harness stall), so it is reported as such rather than as a sleep.
|
|
80
126
|
const cause = (run?.sleptMs ?? 0) > SLEEP_SPAN_TOLERANCE_MS
|
|
81
127
|
? "the host was asleep"
|
|
82
|
-
: "no host sleep measured: the host was starved or
|
|
128
|
+
: "no host sleep measured: the host was starved or tumwater stalled";
|
|
83
129
|
return (`timed out after ${oneDecimalSeconds(bound + late)}s (its ${bound / 1000}s deadline fired ` +
|
|
84
130
|
`${oneDecimalSeconds(late)}s late: ${cause})`);
|
|
85
131
|
}
|
|
@@ -89,7 +135,7 @@ const STARVED_DEADLINE_MAX_FACTOR = 2;
|
|
|
89
135
|
/** The deadline a merge-scope check runs under: `baseMs` stretched by the host's load-to-cores
|
|
90
136
|
* ratio (at most STARVED_DEADLINE_MAX_FACTOR) when the load average exceeds the core count,
|
|
91
137
|
* else `baseMs` itself. 46 of 50 merge-scope rejections in one window were the 300 s deadline
|
|
92
|
-
* firing on time under the
|
|
138
|
+
* firing on time under the team's own load, with a passing tail at 299.97 s — the check was
|
|
93
139
|
* measuring the load, not the tree (BUGS.md 2026-10-12). */
|
|
94
140
|
export function starvedDeadlineMs(baseMs, load) {
|
|
95
141
|
if (!(load.cpus > 0) || !(load.load1 > load.cpus))
|
|
@@ -97,7 +143,7 @@ export function starvedDeadlineMs(baseMs, load) {
|
|
|
97
143
|
return Math.round(baseMs * Math.min(load.load1 / load.cpus, STARVED_DEADLINE_MAX_FACTOR));
|
|
98
144
|
}
|
|
99
145
|
/** The note an on-time deadline kill carries when the host was starved: the load average
|
|
100
|
-
* beyond the core count means the check shared the machine with the rest of the
|
|
146
|
+
* beyond the core count means the check shared the machine with the rest of the team, so the
|
|
101
147
|
* 300 s kill is a statement about the deadline under load, not a measured verdict on the
|
|
102
148
|
* change (BUGS.md 2026-10-12). Empty when the host had a core to spare. */
|
|
103
149
|
export function loadedHostPhrase(load) {
|
|
@@ -136,7 +182,7 @@ export function installFailedPhrase(label, install, proceeding) {
|
|
|
136
182
|
/** Did this outcome make no verdict about the tree? runScopedBuildCheck records that verdict-
|
|
137
183
|
* less shape explicitly (`unverified: true`) at the merge scopes; a gate-scope failure carries
|
|
138
184
|
* only the run's own sleep evidence (BuildCheckRun.sleptMs), so both spellings count. The
|
|
139
|
-
* consumers that attribute a red — review.ts's gate,
|
|
185
|
+
* consumers that attribute a red — review.ts's gate, merge-check-failures.ts's attribution, the
|
|
140
186
|
* red-main baseline — ask this instead of re-deriving the evidence, so the attribution policy
|
|
141
187
|
* cannot drift between them (BUGS.md 2026-09-30). */
|
|
142
188
|
export function unverifiedTreeOutcome(outcome) {
|
|
@@ -184,15 +230,15 @@ function buildCheckRunFields(outcome) {
|
|
|
184
230
|
...(run.sleptMs === undefined ? {} : { sleptMs: run.sleptMs }),
|
|
185
231
|
};
|
|
186
232
|
}
|
|
187
|
-
/** The one home of the build_check event's shape — `{
|
|
233
|
+
/** The one home of the build_check event's shape — `{ source, type: "build_check", scope,
|
|
188
234
|
* status, script, durationMs }` plus the run-timing fields (buildCheckRunFields above) —
|
|
189
235
|
* so the feed's most expensive event type cannot drift a field between its four surfaces:
|
|
190
236
|
* runScopedBuildCheck's two priced events in build-check-scoped.ts (the killed retry and the
|
|
191
237
|
* final verdict) and the red-main baseline loggers (main-red.ts's baselineCheckLogger,
|
|
192
238
|
* redeploy.ts's redeploy mirror), which all paid the run whose timings the event carries. */
|
|
193
|
-
export function buildCheckEvent(
|
|
239
|
+
export function buildCheckEvent(source, scope, outcome, durationMs) {
|
|
194
240
|
return {
|
|
195
|
-
|
|
241
|
+
source,
|
|
196
242
|
type: "build_check",
|
|
197
243
|
scope,
|
|
198
244
|
status: outcome.status,
|
|
@@ -213,11 +259,11 @@ export function buildCheckEvent(loop, scope, outcome, durationMs) {
|
|
|
213
259
|
: {}),
|
|
214
260
|
};
|
|
215
261
|
}
|
|
216
|
-
/** The onRun hook that logs one baseline check's build_check event through `log` under `
|
|
262
|
+
/** The onRun hook that logs one baseline check's build_check event through `log` under `source`:
|
|
217
263
|
* main-red.ts's gate logger (logEventBestEffort) and redeploy.ts's green-check and witness hooks
|
|
218
264
|
* all turn the same run shape into the same baseline event, so that mapping lives here once
|
|
219
265
|
* instead of as three hand-rolled closures. `log` receives the event alone; a caller binds its
|
|
220
266
|
* root (logEventBestEffort) or its RedeployEvent sink. */
|
|
221
|
-
export function baselineCheckEventLogger(
|
|
222
|
-
return ({ outcome, durationMs }) => log(buildCheckEvent(
|
|
267
|
+
export function baselineCheckEventLogger(source, log) {
|
|
268
|
+
return ({ outcome, durationMs }) => log(buildCheckEvent(source, "baseline", outcome, durationMs));
|
|
223
269
|
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* (clipReason/MAX_REASON_CHARS, shared with
|
|
6
|
-
* output tail (clipBuildTail), the one-line headline
|
|
7
|
-
* (failureHeadline), the check's human-facing command name
|
|
8
|
-
* red check hands its author (checkFailureReasons). Every
|
|
9
|
-
* review gate, the
|
|
10
|
-
* "which line is the headline" and "how much output survives" have one
|
|
11
|
-
* back-reference to build-check.ts for BuildCheckOutcome (no runtime cycle);
|
|
12
|
-
* from build-check-detect.ts. */
|
|
1
|
+
// vocabulary: plain
|
|
2
|
+
/** Pure formatting and description of a build check's failure output — split out of build-check.ts,
|
|
3
|
+
* which keeps the execution side (detect → run → classify, the toolchain probe, the process-group
|
|
4
|
+
* runner). This module turns a check's raw combined output and a BuildCheckOutcome into the machine
|
|
5
|
+
* text that is persisted and shown: the per-line clip cap (clipReason/MAX_REASON_CHARS, shared with
|
|
6
|
+
* review-verdict.ts's parseVerdict), the clipped output tail (clipBuildTail), the one-line headline
|
|
7
|
+
* a warning names the failure by (failureHeadline), the check's human-facing command name
|
|
8
|
+
* (describeCheck), and the reasons a red check hands its author (checkFailureReasons). Every
|
|
9
|
+
* consumer of a check's tail — the review gate, the merge path, the red-main gate, the tick prompt
|
|
10
|
+
* — imports from here, so "which line is the headline" and "how much output survives" have one
|
|
11
|
+
* answer each. Type-only back-reference to build-check.ts for BuildCheckOutcome (no runtime cycle);
|
|
12
|
+
* BuildCheck comes from build-check-detect.ts. */
|
|
13
13
|
import { truncate } from "../text/text.js";
|
|
14
14
|
/** Per-reason length cap with ellipsis — bounds one line of machine-generated or reviewer
|
|
15
15
|
* text so it cannot bloat persisted state (shared by clipBuildTail here and parseVerdict in
|
|
@@ -52,7 +52,7 @@ export function clipBuildTail(output) {
|
|
|
52
52
|
* window when it would otherwise be cut), so a check that died on an unhandled rejection ends
|
|
53
53
|
* mid-stack and the tail's FIRST line is a frame: every red-main warning logged before
|
|
54
54
|
* 2026-09-18 read "main <sha> is red (test: at process.processTicksAndRejections
|
|
55
|
-
* (node:internal/...))" — where, never what, which is why a false red that blocked the
|
|
55
|
+
* (node:internal/...))" — where, never what, which is why a false red that blocked the team
|
|
56
56
|
* for hours could not be diagnosed from the event feed at all (BUGS.md). Prefer the first line
|
|
57
57
|
* that is not framing — a stack frame, or node:test's summary/framing lines (below) — and fall
|
|
58
58
|
* back to the tail's first line when every line is framing, so a caller always has something
|
|
@@ -95,13 +95,12 @@ export function failureHeadline(tail) {
|
|
|
95
95
|
export function describeCheck(check) {
|
|
96
96
|
return check.kind === "npm" ? `\`npm run ${check.script}\`` : `\`${check.command}\``;
|
|
97
97
|
}
|
|
98
|
-
/** The machine-generated reasons a red check hands its author: the headline joined to the rest
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* single landing's in-lock check. */
|
|
98
|
+
/** The machine-generated reasons a red check hands its author: the headline joined to the rest of
|
|
99
|
+
* the clipped tail, so the compiler error sits right after it in the injected next-tick note. The
|
|
100
|
+
* headline is failureHeadline's — the first line that is not a stack frame — not outputTail[0]: a
|
|
101
|
+
* suite that dies on an unhandled rejection opens mid-stack, and naming the frame tells the author
|
|
102
|
+
* where it broke, never what (BUGS.md 2026-09-19). One definition for every red that rejects a
|
|
103
|
+
* change: the gate's pre-check, a batch's single-change red, and a single merge's in-lock check. */
|
|
105
104
|
export function checkFailureReasons(check, outcome) {
|
|
106
105
|
const tail = outcome.outputTail ?? [];
|
|
107
106
|
// An unverified rejection already says what happened — the check never finished, the tree is
|
|
@@ -110,11 +109,11 @@ export function checkFailureReasons(check, outcome) {
|
|
|
110
109
|
// failure that never happened (BUGS.md 2026-09-28). The empty-tail fallback keeps the safe
|
|
111
110
|
// default should a remap ever lose its reason line.
|
|
112
111
|
if (outcome.unverified) {
|
|
113
|
-
return tail.length > 0 ? tail : [`
|
|
112
|
+
return tail.length > 0 ? tail : [`check failed (${describeCheck(check)})`];
|
|
114
113
|
}
|
|
115
114
|
const headline = failureHeadline(tail);
|
|
116
115
|
const what = describeCheck(check);
|
|
117
116
|
return headline !== undefined
|
|
118
|
-
? [`
|
|
119
|
-
: [`
|
|
117
|
+
? [`check failed (${what}): ${headline}`, ...tail.filter((l) => l !== headline)]
|
|
118
|
+
: [`check failed (${what})`];
|
|
120
119
|
}
|