@coreplane/switchboard 1.251.0 → 1.253.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/dist/assets/config/config.example.yaml +28 -2
- package/dist/assets/deploy/cloudflare/preflight.mjs +19 -21
- package/dist/assets/deploy/cloudflare/worker.ts +6 -3
- package/dist/assets/deploy/cloudflare-memory/runMetricsSink.ts +31 -0
- package/dist/assets/deploy/cloudflare-memory/worker.ts +223 -38
- package/dist/assets/deploy/cloudflare-memory/wrangler.template.jsonc +12 -0
- package/dist/assets/deploy/cloudflare-resident/memoryGuard.ts +212 -0
- package/dist/assets/deploy/cloudflare-resident/refresh.ts +1 -1
- package/dist/assets/deploy/cloudflare-resident/worker.ts +317 -56
- package/dist/assets/deploy/cloudflare-sandbox/Dockerfile +4 -2
- package/dist/assets/deploy/cloudflare-sandbox/runtime-supervisor.d.mts +31 -0
- package/dist/assets/deploy/cloudflare-sandbox/runtime-supervisor.mjs +119 -0
- package/dist/assets/deploy/profile.example.json +1 -1
- package/dist/assets/package-lock.json +3 -3
- package/dist/assets/package.json +3 -2
- package/dist/assets/project.json +13 -9
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/agents/registry.ts +5 -5
- package/dist/assets/src/core/authz/policy.ts +8 -0
- package/dist/assets/src/core/budgets.ts +39 -0
- package/dist/assets/src/core/coordinator/contract.ts +93 -1
- package/dist/assets/src/core/coordinator/driver.ts +236 -21
- package/dist/assets/src/core/drain.ts +50 -0
- package/dist/assets/src/core/memory/engine.ts +123 -4
- package/dist/assets/src/core/memory/scorer.ts +12 -4
- package/dist/assets/src/core/memory/types.ts +73 -12
- package/dist/assets/src/core/modelCard.ts +32 -4
- package/dist/assets/src/core/modelPricing.ts +111 -1
- package/dist/assets/src/core/modelProxy/usage.ts +88 -0
- package/dist/assets/src/core/modelRegistry.ts +15 -1
- package/dist/assets/src/core/refusal.ts +4 -7
- package/dist/assets/src/core/reviewVerdict.ts +4 -0
- package/dist/assets/src/core/runEvents.ts +82 -3
- package/dist/assets/src/core/runFriction.ts +8 -2
- package/dist/assets/src/core/runLedger/types.ts +11 -0
- package/dist/assets/src/core/runMetrics.ts +237 -0
- package/dist/assets/src/core/runRecord.ts +46 -0
- package/dist/assets/src/core/runUsage.ts +67 -13
- package/dist/assets/src/core/schedules.ts +3 -0
- package/dist/assets/src/core/ship/contract.ts +34 -14
- package/dist/assets/src/core/ship/coordinator.ts +485 -55
- package/dist/assets/src/core/ship/renewal.ts +10 -5
- package/dist/assets/src/core/trace/attrs.ts +24 -0
- package/dist/assets/src/core/types.ts +5 -5
- package/dist/assets/src/core/verbosity.ts +48 -0
- package/dist/assets/src/deploy/liveGate.ts +40 -13
- package/dist/assets/src/deploy/profile.ts +16 -0
- package/dist/assets/src/deploy/restart.ts +11 -12
- package/dist/assets/src/execution/residentDepCache.ts +50 -1
- package/dist/assets/src/execution/residentDepsStore.ts +40 -2
- package/dist/assets/src/execution/residentRefresh.ts +55 -3
- package/dist/assets/src/execution/residentSteps.ts +4 -0
- package/dist/assets/src/execution/sandboxErrors.ts +8 -0
- package/dist/assets/web/dist/.vite/manifest.json +444 -430
- package/dist/assets/web/dist/assets/{AppShell-DAtHiuI6.js → AppShell-Bmz8nzz3.js} +1 -1
- package/dist/assets/web/dist/assets/{CostsPage-BoKzYa4B.js → CostsPage-CxTKdpzU.js} +1 -1
- package/dist/assets/web/dist/assets/{DeliveryPage-DUXd-Sl-.js → DeliveryPage-C7jaEJip.js} +1 -1
- package/dist/assets/web/dist/assets/HomePage-cWbupQzy.js +3 -0
- package/dist/assets/web/dist/assets/{InputMenu-C6aPpe30.js → InputMenu-C9E7YoII.js} +1 -1
- package/dist/assets/web/dist/assets/{NotFoundPage-DtE-GgTk.js → NotFoundPage-4nlrq7DP.js} +1 -1
- package/dist/assets/web/dist/assets/{PendingTurnRow-DDhMhrI7.js → PendingTurnRow-C-pUgyyX.js} +1 -1
- package/dist/assets/web/dist/assets/PlanePage-BNy4tSgJ.js +1 -0
- package/dist/assets/web/dist/assets/{ResidentDetailPage-BnEoOnGQ.js → ResidentDetailPage-Dbtiq6_J.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentsIndexPage-Dxpgf-l-.js → ResidentsIndexPage-CKtz9H1o.js} +1 -1
- package/dist/assets/web/dist/assets/RunFoldRow-sYFtuK2G.js +1 -0
- package/dist/assets/web/dist/assets/RunRoutePage-Clh68nw9.js +9 -0
- package/dist/assets/web/dist/assets/RunsIndexPage-DPDbcOA_.js +1 -0
- package/dist/assets/web/dist/assets/{RunsTabs-YSbUu5py.js → RunsTabs-DotpuCSP.js} +1 -1
- package/dist/assets/web/dist/assets/{ScheduledPage-B_GgeJrb.js → ScheduledPage-C8FYoZy5.js} +1 -1
- package/dist/assets/web/dist/assets/{SettingSelect-BIzAsLk1.js → SettingSelect-CSiBQCSX.js} +1 -1
- package/dist/assets/web/dist/assets/{SettingsPage-BXX4R113.js → SettingsPage-CkYIhLUl.js} +1 -1
- package/dist/assets/web/dist/assets/{StatusDot-BOaw8le9.js → StatusDot-C0TUgnC1.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-DYZZ4l4V.js → Tooltip-CN5cZdl3.js} +1 -1
- package/dist/assets/web/dist/assets/{UnitRoutePage-BaSW5Odq.js → UnitRoutePage-D_EtAl8r.js} +1 -1
- package/dist/assets/web/dist/assets/{angular-html-BjeQZdCq.js → angular-html-qW40ZL9x.js} +1 -1
- package/dist/assets/web/dist/assets/{angular-ts-ZGdORrL2.js → angular-ts-PUtZU8Cq.js} +1 -1
- package/dist/assets/web/dist/assets/{apl-DhvV_X93.js → apl-D3fC7Qjd.js} +1 -1
- package/dist/assets/web/dist/assets/{astro-DiRSE4Ug.js → astro-Bi3JFHhh.js} +1 -1
- package/dist/assets/web/dist/assets/{blade-D8Fr5Wvq.js → blade-CSpsf9Z9.js} +1 -1
- package/dist/assets/web/dist/assets/budgets-CbIyPAER.js +1 -0
- package/dist/assets/web/dist/assets/{c-BwbC64D4.js → c-DE_b1oWP.js} +1 -1
- package/dist/assets/web/dist/assets/{chapel-DgmqRK97.js → chapel-CWXd9giL.js} +1 -1
- package/dist/assets/web/dist/assets/{cobol-DJod2RNK.js → cobol-CnMOmWNF.js} +1 -1
- package/dist/assets/web/dist/assets/{coffee-D1Y2CgBY.js → coffee-B9WvURu8.js} +1 -1
- package/dist/assets/web/dist/assets/{cpp-CsO97YOM.js → cpp-BZAJXlWI.js} +1 -1
- package/dist/assets/web/dist/assets/{crystal-CJ7m2tOc.js → crystal-Bm48wAlb.js} +1 -1
- package/dist/assets/web/dist/assets/{css-B2M-NKoj.js → css-WtMeC3ab.js} +1 -1
- package/dist/assets/web/dist/assets/{dist-BCVXeBJ9.js → dist-B1Mf2JbM.js} +2 -2
- package/dist/assets/web/dist/assets/{edge-CFVW-m9B.js → edge-BmwNa6Ss.js} +1 -1
- package/dist/assets/web/dist/assets/{elixir-B2mtRgCr.js → elixir-BotfP-Mn.js} +1 -1
- package/dist/assets/web/dist/assets/{elm-2teVAwCi.js → elm-DAFc6laN.js} +1 -1
- package/dist/assets/web/dist/assets/{erb-LAbeNqen.js → erb-QkOON3bB.js} +1 -1
- package/dist/assets/web/dist/assets/{git-rebase-DnhzH_sp.js → git-rebase-BPs-dd73.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-js-DPKjXf1-.js → glimmer-js-D8eooepk.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-ts-CABXnt5z.js → glimmer-ts-B0bRiDVg.js} +1 -1
- package/dist/assets/web/dist/assets/{glsl-Dz4-3-gJ.js → glsl-Bv6bMe1n.js} +1 -1
- package/dist/assets/web/dist/assets/{graphql-ByWUD1DL.js → graphql-Bh9y-n9o.js} +1 -1
- package/dist/assets/web/dist/assets/{hack-CdmOK2K-.js → hack-DSEbn7Ir.js} +1 -1
- package/dist/assets/web/dist/assets/{haml-DzegMpd2.js → haml-0NW1-z1f.js} +1 -1
- package/dist/assets/web/dist/assets/{handlebars-B5NeiP0e.js → handlebars-BfgCHFbK.js} +1 -1
- package/dist/assets/web/dist/assets/{html-Zg_NHLv9.js → html-CQHc0gQ5.js} +1 -1
- package/dist/assets/web/dist/assets/{html-derivative-DZf9JNzh.js → html-derivative-B0Tm7wLP.js} +1 -1
- package/dist/assets/web/dist/assets/{http-CVFrQZeN.js → http-GAuU3aOF.js} +1 -1
- package/dist/assets/web/dist/assets/{hurl-L5brWAJe.js → hurl-CrDFbF5B.js} +1 -1
- package/dist/assets/web/dist/assets/indexRow-BxKswdlK.js +1 -0
- package/dist/assets/web/dist/assets/{java-hFphyZ4R.js → java-DYv5KhUe.js} +1 -1
- package/dist/assets/web/dist/assets/{javascript-CAJO1WKX.js → javascript-32SiLGg0.js} +1 -1
- package/dist/assets/web/dist/assets/{jinja-N4A4srXI.js → jinja--gJDUGYa.js} +1 -1
- package/dist/assets/web/dist/assets/{jison-D3BZa_iU.js → jison-BlKLGzZH.js} +1 -1
- package/dist/assets/web/dist/assets/{json-DxD1Qh8W.js → json-BICanD1N.js} +1 -1
- package/dist/assets/web/dist/assets/{jsx-CHhag69S.js → jsx-CgW3-uFT.js} +1 -1
- package/dist/assets/web/dist/assets/{julia-CrhkZ6Tt.js → julia-BErRpwwL.js} +1 -1
- package/dist/assets/web/dist/assets/{just-D0LFOuhq.js → just-CordHv0h.js} +1 -1
- package/dist/assets/web/dist/assets/{latex-BHAlwCXK.js → latex-D7LVPLbi.js} +1 -1
- package/dist/assets/web/dist/assets/{liquid-CkaLfCWh.js → liquid-3ehz2i4U.js} +1 -1
- package/dist/assets/web/dist/assets/{lua-B1h4aAzP.js → lua-C9ppSVrm.js} +1 -1
- package/dist/assets/web/dist/assets/main-Cv0q9y56.js +28 -0
- package/dist/assets/web/dist/assets/main-DKFCM-sy.css +1 -0
- package/dist/assets/web/dist/assets/{marko-D6CG4hvX.js → marko-xBZLUBEJ.js} +1 -1
- package/dist/assets/web/dist/assets/{mdc-CVQzimAU.js → mdc-BJxGoVgD.js} +1 -1
- package/dist/assets/web/dist/assets/{nginx-h-9Ir54h.js → nginx-B-sZmpsb.js} +1 -1
- package/dist/assets/web/dist/assets/{nim-DHVI30sp.js → nim--s9wRABK.js} +1 -1
- package/dist/assets/web/dist/assets/{org-AasAXfMF.js → org-DoUvssoI.js} +1 -1
- package/dist/assets/web/dist/assets/{perl-BVi5aTEy.js → perl-B-vGHKOy.js} +1 -1
- package/dist/assets/web/dist/assets/{php-qOkCeUKr.js → php-CerWx_Xb.js} +1 -1
- package/dist/assets/web/dist/assets/{pug-8TnUGvEX.js → pug-CzKrBMb1.js} +1 -1
- package/dist/assets/web/dist/assets/{qml-CpJbhoxn.js → qml-Bs8L8I6k.js} +1 -1
- package/dist/assets/web/dist/assets/{r-JkUNcAJW.js → r-w6uG6mob.js} +1 -1
- package/dist/assets/web/dist/assets/{razor-CtU4ws5a.js → razor-dz2oj2bd.js} +1 -1
- package/dist/assets/web/dist/assets/{regexp-J2zA6ceN.js → regexp-CfQdprnY.js} +1 -1
- package/dist/assets/web/dist/assets/{rst-CMCvf-yZ.js → rst-DAOaX2tb.js} +1 -1
- package/dist/assets/web/dist/assets/{ruby-ChLBY_iR.js → ruby-QcCe4udB.js} +1 -1
- package/dist/assets/web/dist/assets/{sas-cwn4x2VY.js → sas-BrM4lAAc.js} +1 -1
- package/dist/assets/web/dist/assets/{scss-jSrJlzLI.js → scss-DVbJXoUb.js} +1 -1
- package/dist/assets/web/dist/assets/{shellscript-D5nAd0gs.js → shellscript-09fP2hZf.js} +1 -1
- package/dist/assets/web/dist/assets/{shellsession-BZY4-Tdh.js → shellsession-Qgr8xjZF.js} +1 -1
- package/dist/assets/web/dist/assets/{soy-COppPOKr.js → soy-DIYeNTuz.js} +1 -1
- package/dist/assets/web/dist/assets/{sql-Blc_6dOA.js → sql-CzLR9msV.js} +1 -1
- package/dist/assets/web/dist/assets/sseReplay-BYo2EZKy.js +11 -0
- package/dist/assets/web/dist/assets/{stata-DRv3OY-h.js → stata-zCY1ZzH0.js} +1 -1
- package/dist/assets/web/dist/assets/{surrealql-qMv_eeJ6.js → surrealql-CJIjTRV4.js} +1 -1
- package/dist/assets/web/dist/assets/{svelte-d1Edb6Rz.js → svelte-C5OTPFZg.js} +1 -1
- package/dist/assets/web/dist/assets/{templ-CUMSIOXG.js → templ-DYj-UICF.js} +1 -1
- package/dist/assets/web/dist/assets/{tex-BGZQA-Ei.js → tex-xcie-3Gd.js} +1 -1
- package/dist/assets/web/dist/assets/{ts-tags-Z9ulxVsq.js → ts-tags-CoN86VTt.js} +1 -1
- package/dist/assets/web/dist/assets/{tsx-oI4bI3Vf.js → tsx-n3-No_Br.js} +1 -1
- package/dist/assets/web/dist/assets/{twig-D_RXwBIX.js → twig-DPiJ7Vp1.js} +1 -1
- package/dist/assets/web/dist/assets/{typescript-BgqQDYoN.js → typescript-BP6HBaOp.js} +1 -1
- package/dist/assets/web/dist/assets/{typst-5MW-KRDd.js → typst-Do9ZjsVP.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-CZhHHsPm.js → vue-CzAeXNRY.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-html-DlYy2d8v.js → vue-html-D7iYmYjd.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-vine-CJDnx-qn.js → vue-vine-DtrMRsJx.js} +1 -1
- package/dist/assets/web/dist/assets/{xml-BRBDk65Q.js → xml-DGJHo1c0.js} +1 -1
- package/dist/assets/web/dist/assets/{xsl-BlCykPxo.js → xsl-Q00kvNgW.js} +1 -1
- package/dist/assets/web/dist/assets/{yaml-BqW48szf.js → yaml-F-UGKaiO.js} +1 -1
- package/dist/cli.js +5623 -2455
- package/package.json +1 -1
- package/dist/assets/deploy/cloudflare-sandbox/runtime-supervisor.sh +0 -68
- package/dist/assets/web/dist/assets/HomePage-mSiqEEcN.js +0 -2
- package/dist/assets/web/dist/assets/RunFoldRow-CSo4-vld.js +0 -1
- package/dist/assets/web/dist/assets/RunRoutePage-9klVWhSF.js +0 -9
- package/dist/assets/web/dist/assets/RunsIndexPage-BplMIgaw.js +0 -1
- package/dist/assets/web/dist/assets/budgets-BvWYKPsY.js +0 -1
- package/dist/assets/web/dist/assets/indexRow-Bde9OZxG.js +0 -1
- package/dist/assets/web/dist/assets/main-Dkcbtu3u.js +0 -28
- package/dist/assets/web/dist/assets/main-zbP_dTjR.css +0 -1
- package/dist/assets/web/dist/assets/sseReplay-DPwdsaok.js +0 -9
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
// arrive in the input rather than from the agent registry.
|
|
31
31
|
|
|
32
32
|
import type { ShipRoundOutcome } from "../runEvents.js";
|
|
33
|
+
import { shows, type Verbosity } from "../verbosity.js";
|
|
33
34
|
import type { Handoff, HandoffLanded } from "./handoff.js";
|
|
34
35
|
import { progressOf, renderRenewal, renewalDecision, type PushedHeadFact, type RenewalDecision } from "./renewal.js";
|
|
35
36
|
import type { RunStatus } from "../runRecord.js";
|
|
@@ -51,6 +52,7 @@ import {
|
|
|
51
52
|
carve,
|
|
52
53
|
DEFAULT_GRANT,
|
|
53
54
|
loopPosition,
|
|
55
|
+
MERGE_WAIT_ASK_MINUTES,
|
|
54
56
|
MINUTE_MS,
|
|
55
57
|
SHIP_WAIT,
|
|
56
58
|
type Carve,
|
|
@@ -430,8 +432,19 @@ export type Brief =
|
|
|
430
432
|
round: number;
|
|
431
433
|
/** The previous review round's run and the coding run that answered its findings, for a re-review. */
|
|
432
434
|
prior?: { reviewRunId: string; codingRunId?: string };
|
|
435
|
+
/** The previous round's check findings (record 0055): they sit on no
|
|
436
|
+
* run's record, so the brief carries them beside `prior` by value. */
|
|
437
|
+
checks?: Finding[];
|
|
433
438
|
}
|
|
434
|
-
| {
|
|
439
|
+
| {
|
|
440
|
+
kind: "findings";
|
|
441
|
+
unit: string;
|
|
442
|
+
pr: number;
|
|
443
|
+
reviewRunId: string;
|
|
444
|
+
/** The round's check findings (record 0055), carried by value — the
|
|
445
|
+
* review run's record holds only the reviewer's own findings. */
|
|
446
|
+
checks?: Finding[];
|
|
447
|
+
};
|
|
435
448
|
|
|
436
449
|
export interface PrRef {
|
|
437
450
|
number: number;
|
|
@@ -462,6 +475,12 @@ export type CoordinatorAction =
|
|
|
462
475
|
pr?: number;
|
|
463
476
|
}
|
|
464
477
|
| { type: "merge"; step: string; prNumber: number; headSha: string }
|
|
478
|
+
/** Read the check runs at the reviewed head (record 0055, the round verdict):
|
|
479
|
+
* the merge door's own reading, folded into the round. `retry` names the
|
|
480
|
+
* failed checks whose one flake re-run the machine is spending: the bot
|
|
481
|
+
* re-runs their failed jobs (the CI retry the deploy already uses) instead
|
|
482
|
+
* of reading. */
|
|
483
|
+
| { type: "checks"; step: string; prNumber: number; headSha: string; retry?: string[] }
|
|
465
484
|
/** Wait for the intake's checks-settled event at the approved head, bounded as the fallback. */
|
|
466
485
|
| { type: "wait-checks"; step: string; headSha: string; timeoutMs: number }
|
|
467
486
|
| { type: "sleep"; step: string; ms: number }
|
|
@@ -529,6 +548,12 @@ export type PrCheck =
|
|
|
529
548
|
autoMergeEnabled?: boolean;
|
|
530
549
|
/** The check runs at the head, when the read asked for them (the ending's facts). */
|
|
531
550
|
checks?: CommitChecksFacts;
|
|
551
|
+
/** GitHub's `mergeable_state`, when the read asked for the facts — `dirty`
|
|
552
|
+
* keeps the report from calling a conflicting head merge-ready (item 9). */
|
|
553
|
+
mergeableState?: string;
|
|
554
|
+
/** The head's self-declared fix-up commit subjects (`fixup!`/`squash!`/`amend!`),
|
|
555
|
+
* when the read asked for the facts — a head carrying one is not in the ready state. */
|
|
556
|
+
fixupCommits?: string[];
|
|
532
557
|
}
|
|
533
558
|
| { state: "merged"; prNumber: number; url: string; sha: string; mergedAt: string };
|
|
534
559
|
|
|
@@ -548,9 +573,56 @@ export type StepReturn =
|
|
|
548
573
|
// fired, or a person merged — so the runner merged nothing (`by: other`).
|
|
549
574
|
| { type: "merge"; step: string; outcome: "merged"; by: "other"; sha: string; mergedAt: string; at: number }
|
|
550
575
|
| { type: "merge"; step: string; outcome: "pending" | "refused"; reason: string; at: number }
|
|
576
|
+
/** The checks read at the reviewed head; `checks` absent means GitHub could
|
|
577
|
+
* not be read, which the machine treats as pending (record 0055). A retry
|
|
578
|
+
* ask answers `retried` instead: whether the bot dispatched the re-run —
|
|
579
|
+
* false means there is nothing to wait for at the unchanged head. */
|
|
580
|
+
| { type: "checks"; step: string; checks?: RoundChecks; retried?: boolean; at: number }
|
|
551
581
|
| { type: "wait-checks"; step: string; outcome: "event" | "timeout" }
|
|
552
582
|
| { type: "sleep"; step: string };
|
|
553
583
|
|
|
584
|
+
/** One failed check run at the reviewed head, as the round's checks step reads
|
|
585
|
+
* it (record 0055): the name, GitHub's conclusion, the run's URL, and whether
|
|
586
|
+
* the bot's classifier suspects a flake — a test timeout or runner stall on a
|
|
587
|
+
* shard whose test files the pull request's changed paths never touch. */
|
|
588
|
+
export interface CheckFailure {
|
|
589
|
+
name: string;
|
|
590
|
+
conclusion: string;
|
|
591
|
+
url?: string;
|
|
592
|
+
flakeSuspect?: boolean;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/** The check runs at the reviewed head as the round's checks step reads them. */
|
|
596
|
+
export interface RoundChecks {
|
|
597
|
+
total: number;
|
|
598
|
+
pending: string[];
|
|
599
|
+
failed: CheckFailure[];
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/** A failed check as a finding of the round (record 0055): a finding row like
|
|
603
|
+
* a reviewer's — id `check:<name>`, the check's name as the file, severity
|
|
604
|
+
* `blocking` so the level in force always counts it, the conclusion and URL
|
|
605
|
+
* as the title — so the ledger, the findings brief and the dispositions
|
|
606
|
+
* carry it exactly as a reviewer's. */
|
|
607
|
+
export function checkFinding(f: CheckFailure): Finding {
|
|
608
|
+
return {
|
|
609
|
+
id: `check:${f.name}`,
|
|
610
|
+
severity: "blocking",
|
|
611
|
+
file: f.name,
|
|
612
|
+
title: `CI check failed (${f.conclusion})${f.url !== undefined ? ` — ${f.url}` : ""}`,
|
|
613
|
+
check: true,
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/** The check findings of a review round: the rows the checks step appended to
|
|
618
|
+
* the round's findings, told apart by provenance — the `check` flag only
|
|
619
|
+
* `checkFinding` sets, never the id: a reviewer's id is a free string, and a
|
|
620
|
+
* re-review brief names the prior check findings by id, so a reviewer
|
|
621
|
+
* plausibly reuses `check:…` in its own verdict — a check finding sits on no
|
|
622
|
+
* run's record, so a brief carries them by value. */
|
|
623
|
+
export const checkFindingsOf = (findings: readonly Finding[] | undefined): Finding[] =>
|
|
624
|
+
(findings ?? []).filter((f) => f.check === true);
|
|
625
|
+
|
|
554
626
|
/** How one unit's pipeline ended — the truthful vocabulary the ship pipeline
|
|
555
627
|
* has, plus the merge's own: `merged` by the runner, or found merged (`by:
|
|
556
628
|
* other` — a person's merge, or an earlier attempt's that died after it, so
|
|
@@ -624,7 +696,46 @@ export type UnitEnding =
|
|
|
624
696
|
}
|
|
625
697
|
| { kind: "no_verdict"; round: RoundRef; reviewRounds: number; finalReply?: string }
|
|
626
698
|
| { kind: "interrupted"; round: RoundRef; runId: string; reviewRounds: number }
|
|
627
|
-
| { kind: "refused"; refusal: string; message?: string; round: RoundRef; reviewRounds: number }
|
|
699
|
+
| { kind: "refused"; refusal: string; message?: string; round: RoundRef; reviewRounds: number }
|
|
700
|
+
/** The unit idles instead of ending (record 0051): with the resolved
|
|
701
|
+
* `ship.idleDays` above zero, `end()` wraps an idling kind — every kind but
|
|
702
|
+
* the four ended ones (`merged`, `already_landed`, `merge_ready`,
|
|
703
|
+
* `refused`) — in this ending: the old kind as `why`, the old kind's
|
|
704
|
+
* report unchanged, and what a continuation needs (the renewals the grant
|
|
705
|
+
* still holds — unspent: the wake spends one, this plan's fifth unit — the
|
|
706
|
+
* head to continue from, the last coding child's run, the spend and its
|
|
707
|
+
* handoff). `runId` is absent when the pipeline capped before any coding
|
|
708
|
+
* child ran. */
|
|
709
|
+
| {
|
|
710
|
+
kind: "idle";
|
|
711
|
+
why: IdleWhy;
|
|
712
|
+
/** The ending the idle stands in for, whole: its report is rendered per
|
|
713
|
+
* copy at that copy's level — the row's and the board's in full, the
|
|
714
|
+
* thread's at the request's verbosity — never once at mapping time. */
|
|
715
|
+
idled: Exclude<UnitEnding, { kind: "idle" }>;
|
|
716
|
+
renewalsLeft: number;
|
|
717
|
+
from?: string;
|
|
718
|
+
runId?: string;
|
|
719
|
+
spendUsd: number | null;
|
|
720
|
+
handoff?: Handoff;
|
|
721
|
+
round?: RoundRef;
|
|
722
|
+
reviewRounds: number;
|
|
723
|
+
};
|
|
724
|
+
|
|
725
|
+
/** The kinds that idle: every ending but the four ended ones — the unit is
|
|
726
|
+
* unfinished (a cap, a stop, an abort, a refused merge, a segment's end) and
|
|
727
|
+
* a reply could continue it. `merged`, `already_landed`, `merge_ready` and
|
|
728
|
+
* `refused` never idle: the first two are done, merge-ready waits only for a
|
|
729
|
+
* person's merge, and a refused child would be refused again. */
|
|
730
|
+
export type IdleWhy = Exclude<UnitEnding["kind"], "idle" | "merged" | "already_landed" | "merge_ready" | "refused">;
|
|
731
|
+
|
|
732
|
+
const NEVER_IDLES: ReadonlySet<UnitEnding["kind"]> = new Set([
|
|
733
|
+
"idle",
|
|
734
|
+
"merged",
|
|
735
|
+
"already_landed",
|
|
736
|
+
"merge_ready",
|
|
737
|
+
"refused",
|
|
738
|
+
]);
|
|
628
739
|
|
|
629
740
|
/** What a transition tells the driver beyond the next action: a round boundary
|
|
630
741
|
* the card draws (`shipRoundHeader`) and the run stream records, and the end. */
|
|
@@ -698,6 +809,9 @@ export interface UnitPipelineInput {
|
|
|
698
809
|
* it yet: the renewal decision is the segment's end, not this unit's. */
|
|
699
810
|
grant?: Grant;
|
|
700
811
|
grantSource?: GrantSource;
|
|
812
|
+
/** The request's verbosity (routing-and-config item 28): the level the
|
|
813
|
+
* thread's copy of the unit report is rendered at; absent reads as quiet. */
|
|
814
|
+
verbosity?: Verbosity;
|
|
701
815
|
/** The segment this pipeline runs (decision 0046, Renewal): absent for the
|
|
702
816
|
* first; a renewal's carries its number, the renewals spent before it, the
|
|
703
817
|
* session's spend so far, the sha it continues from and the previous
|
|
@@ -708,6 +822,11 @@ export interface UnitPipelineInput {
|
|
|
708
822
|
* `plan` with an id and no `path` — whose unit runs in the requesting
|
|
709
823
|
* thread and is re-issued with the request's own text, never a plan path. */
|
|
710
824
|
generated: boolean;
|
|
825
|
+
/** The idle flag (record 0051; agent-ship item 8): `ship.idleDays` as the
|
|
826
|
+
* ship fork resolved it onto the instance — above zero, an idling kind ends
|
|
827
|
+
* `idle` instead; zero or absent is today's behavior byte for byte. The
|
|
828
|
+
* wait itself lands with this plan's fifth unit. */
|
|
829
|
+
idleDays?: number;
|
|
711
830
|
/** Resume at review: an open pull request of ship's own the requester named. */
|
|
712
831
|
resume?: { pr: number; headSha?: string; url?: string };
|
|
713
832
|
/** The head the previous attempt's coding child last pushed (a
|
|
@@ -747,6 +866,36 @@ type Phase =
|
|
|
747
866
|
}
|
|
748
867
|
| { at: "merge"; pr: PrRef; headSha: string; n: number; since: number; waitMs: number }
|
|
749
868
|
| { at: "merge-wait"; pr: PrRef; headSha: string; n: number; since: number; waitMs: number }
|
|
869
|
+
/** The round's checks step (record 0055): the check runs at the reviewed head
|
|
870
|
+
* are read after an approve settles, before merge_ready or the merge door.
|
|
871
|
+
* `graced`: a head with no check reported has had its one-chunk grace;
|
|
872
|
+
* `retried`: the one flake re-run is spent; `retry` names the failed checks
|
|
873
|
+
* the next ask re-runs instead of reading. */
|
|
874
|
+
| {
|
|
875
|
+
at: "checks";
|
|
876
|
+
round: RoundRef;
|
|
877
|
+
prNumber: number;
|
|
878
|
+
headSha: string;
|
|
879
|
+
n: number;
|
|
880
|
+
since: number;
|
|
881
|
+
waitMs: number;
|
|
882
|
+
graced: boolean;
|
|
883
|
+
retried: boolean;
|
|
884
|
+
retry?: string[];
|
|
885
|
+
}
|
|
886
|
+
/** Waiting on `checks-settled-<head>` between two checks reads, in the merge
|
|
887
|
+
* wait's own chunks — the intake wakes the machine when the head settles. */
|
|
888
|
+
| {
|
|
889
|
+
at: "checks-wait";
|
|
890
|
+
round: RoundRef;
|
|
891
|
+
prNumber: number;
|
|
892
|
+
headSha: string;
|
|
893
|
+
n: number;
|
|
894
|
+
since: number;
|
|
895
|
+
waitMs: number;
|
|
896
|
+
graced: boolean;
|
|
897
|
+
retried: boolean;
|
|
898
|
+
}
|
|
750
899
|
| { at: "ended" };
|
|
751
900
|
|
|
752
901
|
export interface UnitPipelineState {
|
|
@@ -771,6 +920,10 @@ export interface UnitPipelineState {
|
|
|
771
920
|
readonly findingsRunByRound: Readonly<Record<number, string>>;
|
|
772
921
|
/** The last coding run (round 0's child or a findings step's): its record carries the unit's handoff. */
|
|
773
922
|
readonly lastCodingRunId?: string;
|
|
923
|
+
/** The head the last coding child left the branch at, and its handoff — the
|
|
924
|
+
* continuation facts an `idle` ending carries (record 0051). */
|
|
925
|
+
readonly lastChildHead?: string;
|
|
926
|
+
readonly lastCodingHandoff?: Handoff;
|
|
774
927
|
/** How the budget went so far, accrued as each answer moves the clock. */
|
|
775
928
|
readonly spentMs: ShipBudgetSpent;
|
|
776
929
|
/** The session's dollars so far: the input's from earlier segments plus each
|
|
@@ -879,9 +1032,19 @@ function briefFor(s: UnitPipelineState, round: RoundRef): Brief {
|
|
|
879
1032
|
};
|
|
880
1033
|
}
|
|
881
1034
|
const pr = s.pr!.number;
|
|
882
|
-
if (round.kind === "findings")
|
|
1035
|
+
if (round.kind === "findings") {
|
|
1036
|
+
const checks = checkFindingsOf(s.findingsByRound[round.index]);
|
|
1037
|
+
return {
|
|
1038
|
+
kind: "findings",
|
|
1039
|
+
unit,
|
|
1040
|
+
pr,
|
|
1041
|
+
reviewRunId: s.reviewRunByRound[round.index]!,
|
|
1042
|
+
...(checks.length > 0 ? { checks } : {}),
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
883
1045
|
const priorReview = s.reviewRunByRound[round.index - 1];
|
|
884
1046
|
const priorCoding = s.findingsRunByRound[round.index - 1];
|
|
1047
|
+
const priorChecks = checkFindingsOf(s.findingsByRound[round.index - 1]);
|
|
885
1048
|
return {
|
|
886
1049
|
kind: "review",
|
|
887
1050
|
unit,
|
|
@@ -891,6 +1054,7 @@ function briefFor(s: UnitPipelineState, round: RoundRef): Brief {
|
|
|
891
1054
|
...(priorReview !== undefined
|
|
892
1055
|
? { prior: { reviewRunId: priorReview, ...(priorCoding !== undefined ? { codingRunId: priorCoding } : {}) } }
|
|
893
1056
|
: {}),
|
|
1057
|
+
...(priorChecks.length > 0 ? { checks: priorChecks } : {}),
|
|
894
1058
|
};
|
|
895
1059
|
}
|
|
896
1060
|
|
|
@@ -954,6 +1118,21 @@ export function nextAction(s: UnitPipelineState): CoordinatorAction {
|
|
|
954
1118
|
// when nothing heads the unit's branch (issue 1799).
|
|
955
1119
|
...(s.pr !== undefined ? { pr: s.pr.number } : {}),
|
|
956
1120
|
};
|
|
1121
|
+
case "checks":
|
|
1122
|
+
return {
|
|
1123
|
+
type: "checks",
|
|
1124
|
+
step: `${roundStep(s, p.round)}/checks/${p.n}`,
|
|
1125
|
+
prNumber: p.prNumber,
|
|
1126
|
+
headSha: p.headSha,
|
|
1127
|
+
...(p.retry !== undefined ? { retry: p.retry } : {}),
|
|
1128
|
+
};
|
|
1129
|
+
case "checks-wait":
|
|
1130
|
+
return {
|
|
1131
|
+
type: "wait-checks",
|
|
1132
|
+
step: `${roundStep(s, p.round)}/checks/wait/${p.n}`,
|
|
1133
|
+
headSha: p.headSha,
|
|
1134
|
+
timeoutMs: Math.max(MIN, Math.min(MERGE_WAIT_CHUNK_MS, p.waitMs - (s.clock - p.since))),
|
|
1135
|
+
};
|
|
957
1136
|
case "merge":
|
|
958
1137
|
return { type: "merge", step: `${unit}/merge/${p.n}`, prNumber: p.pr.number, headSha: p.headSha };
|
|
959
1138
|
case "merge-wait":
|
|
@@ -986,7 +1165,55 @@ function addSpend(sum: number | null, cost: number | null | undefined): number |
|
|
|
986
1165
|
}
|
|
987
1166
|
|
|
988
1167
|
function end(s: UnitPipelineState, ending: UnitEnding, notes: CoordinatorNote[] = []): Transition {
|
|
989
|
-
|
|
1168
|
+
const idled = idleEnding(s, ending);
|
|
1169
|
+
const final = idled ?? ending;
|
|
1170
|
+
return { state: { ...s, phase: ENDED, ending: final }, notes: [...notes, { type: "ended", ending: final }] };
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
/** The idle mapping (record 0051; agent-ship item 8): with the input's
|
|
1174
|
+
* `idleDays` above zero an idling kind ends `idle` — the old kind as `why`,
|
|
1175
|
+
* the old ending itself for the report, and the continuation facts off the state
|
|
1176
|
+
* (a `continued` ending's own where it carries them). At zero, undefined:
|
|
1177
|
+
* every ending is byte for byte today's. The round notes keep the old kind's
|
|
1178
|
+
* outcome — the row and the card say what happened. */
|
|
1179
|
+
function idleEnding(s: UnitPipelineState, ending: UnitEnding): Extract<UnitEnding, { kind: "idle" }> | undefined {
|
|
1180
|
+
if ((s.input.idleDays ?? 0) <= 0) return undefined;
|
|
1181
|
+
if (NEVER_IDLES.has(ending.kind)) return undefined;
|
|
1182
|
+
const old = ending as Exclude<UnitEnding, { kind: "idle" | "merged" | "already_landed" | "merge_ready" | "refused" }>;
|
|
1183
|
+
const grant = s.input.grant ?? DEFAULT_GRANT;
|
|
1184
|
+
// Unspent: an idle spends no renewal — the wake's segment does (this plan's
|
|
1185
|
+
// fifth unit) — so the row says what the grant still holds.
|
|
1186
|
+
const renewalsLeft = Math.max(0, grant.renewals - (s.input.session?.renewalsSpent ?? 0));
|
|
1187
|
+
// The head to continue from: a segment's own; a review_pending's reviewed
|
|
1188
|
+
// head — the pull request's, known even when the coding record carried none,
|
|
1189
|
+
// so the re-issue's resume-at-review fact survives the idle; else the last
|
|
1190
|
+
// coding child's recorded head.
|
|
1191
|
+
const from =
|
|
1192
|
+
old.kind === "continued"
|
|
1193
|
+
? old.from
|
|
1194
|
+
: old.kind === "review_pending"
|
|
1195
|
+
? (old.headSha ?? s.lastChildHead)
|
|
1196
|
+
: s.lastChildHead;
|
|
1197
|
+
// The last coding child's run: a segment's own, a dead coding child's own —
|
|
1198
|
+
// a review child that died names nothing here, the coding run before it does.
|
|
1199
|
+
const runId =
|
|
1200
|
+
old.kind === "continued" || (old.kind === "interrupted" && old.round.kind !== "review")
|
|
1201
|
+
? old.runId
|
|
1202
|
+
: s.lastCodingRunId;
|
|
1203
|
+
const handoff = old.kind === "continued" ? old.handoff : s.lastCodingHandoff;
|
|
1204
|
+
const round = "round" in old ? old.round : undefined;
|
|
1205
|
+
return {
|
|
1206
|
+
kind: "idle",
|
|
1207
|
+
why: old.kind,
|
|
1208
|
+
idled: old,
|
|
1209
|
+
renewalsLeft,
|
|
1210
|
+
...(from !== undefined ? { from } : {}),
|
|
1211
|
+
...(runId !== undefined ? { runId } : {}),
|
|
1212
|
+
spendUsd: s.spendUsd,
|
|
1213
|
+
...(handoff !== undefined ? { handoff } : {}),
|
|
1214
|
+
...(round !== undefined ? { round } : {}),
|
|
1215
|
+
reviewRounds: s.reviewRounds,
|
|
1216
|
+
};
|
|
990
1217
|
}
|
|
991
1218
|
|
|
992
1219
|
/** A gated finding as the gate note names it: `F1 (minor)`. */
|
|
@@ -1059,6 +1286,9 @@ function settleCoding(
|
|
|
1059
1286
|
let next: UnitPipelineState = {
|
|
1060
1287
|
...s,
|
|
1061
1288
|
spendUsd: addSpend(s.spendUsd, facts.costUsd),
|
|
1289
|
+
// The continuation facts an idle ending reads off the state (record 0051).
|
|
1290
|
+
...(facts.headSha !== undefined ? { lastChildHead: facts.headSha } : {}),
|
|
1291
|
+
...(facts.handoffLists !== undefined ? { lastCodingHandoff: facts.handoffLists } : {}),
|
|
1062
1292
|
...(facts.pr !== undefined ? { pr: { number: facts.pr.number, url: facts.pr.url } } : {}),
|
|
1063
1293
|
...(round.kind === "findings" && facts.dispositions !== undefined
|
|
1064
1294
|
? {
|
|
@@ -1192,6 +1422,163 @@ function settleReview(
|
|
|
1192
1422
|
);
|
|
1193
1423
|
return enterRound(next, { index: round.index, kind: "findings" }, notes);
|
|
1194
1424
|
}
|
|
1425
|
+
// The round verdict (record 0055): the approve is joined with the check
|
|
1426
|
+
// runs at the reviewed head before merge_ready or the merge door — the
|
|
1427
|
+
// checks step reads them and a failed one becomes a finding of the round.
|
|
1428
|
+
return enterChecks(next, round, notes);
|
|
1429
|
+
}
|
|
1430
|
+
// request_changes: the verdict settled (and posted) — now a stop
|
|
1431
|
+
// short-circuits the findings step, naming the review standing on the pull request.
|
|
1432
|
+
if (mode !== undefined)
|
|
1433
|
+
return end(
|
|
1434
|
+
next,
|
|
1435
|
+
{
|
|
1436
|
+
kind: "stopped",
|
|
1437
|
+
mode,
|
|
1438
|
+
round,
|
|
1439
|
+
reviewRounds: next.reviewRounds,
|
|
1440
|
+
...(facts.finalReply !== undefined ? { finalReply: facts.finalReply } : {}),
|
|
1441
|
+
...(facts.reviewPosted === true ? { postedReview: true } : {}),
|
|
1442
|
+
},
|
|
1443
|
+
notes,
|
|
1444
|
+
);
|
|
1445
|
+
if (next.reviewRounds >= next.input.caps.maxRounds)
|
|
1446
|
+
return end(
|
|
1447
|
+
next,
|
|
1448
|
+
{ kind: "round_cap", maxRounds: next.input.caps.maxRounds, reviewRounds: next.reviewRounds },
|
|
1449
|
+
notes,
|
|
1450
|
+
);
|
|
1451
|
+
// The findings step (agent-ship item 7): the review's findings dispatched into
|
|
1452
|
+
// the unit thread as a coding run, under the review round's index. A run live
|
|
1453
|
+
// there is a person's (this machine awaited its own child's end), so the
|
|
1454
|
+
// spawn answers busy and the step waits under the unit's clock like any round.
|
|
1455
|
+
return enterRound(next, { index: round.index, kind: "findings" }, notes);
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
/** The round's checks step (record 0055): after an approve settles, the check
|
|
1459
|
+
* runs at the reviewed head are read with the merge door's own reading and
|
|
1460
|
+
* folded into the round verdict. With no reviewed head known the step is
|
|
1461
|
+
* skipped as the merge step's guard is, and the unit ends as today. The wait
|
|
1462
|
+
* on a pending head is bounded by the merge wait's own ask, clipped to the
|
|
1463
|
+
* pipeline's remainder. */
|
|
1464
|
+
function enterChecks(s: UnitPipelineState, round: RoundRef, notes: CoordinatorNote[]): Transition {
|
|
1465
|
+
const headSha = normalizeHead(s.lastReviewHead);
|
|
1466
|
+
const pr = s.pr;
|
|
1467
|
+
if (headSha === undefined || pr === undefined) return approveOutcome(s, notes);
|
|
1468
|
+
const waitMs = Math.max(MIN, Math.min(MERGE_WAIT_ASK_MINUTES * MIN, remainingMs(s)));
|
|
1469
|
+
return {
|
|
1470
|
+
state: {
|
|
1471
|
+
...s,
|
|
1472
|
+
phase: {
|
|
1473
|
+
at: "checks",
|
|
1474
|
+
round,
|
|
1475
|
+
prNumber: pr.number,
|
|
1476
|
+
headSha,
|
|
1477
|
+
n: 1,
|
|
1478
|
+
since: s.clock,
|
|
1479
|
+
waitMs,
|
|
1480
|
+
graced: false,
|
|
1481
|
+
retried: false,
|
|
1482
|
+
},
|
|
1483
|
+
},
|
|
1484
|
+
notes,
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
/** What the checks step answered, folded into the round (record 0055). Pure
|
|
1489
|
+
* over the phase: a retry ask waits for the head to settle and reads again;
|
|
1490
|
+
* an unreadable or pending head waits a chunk inside the step's ask and then
|
|
1491
|
+
* proceeds — the ending's facts read names what is still pending; a head with
|
|
1492
|
+
* no check reported waits one chunk of grace and never more; a failed check
|
|
1493
|
+
* becomes a check finding under a round note of its own and the findings step
|
|
1494
|
+
* runs as for any changes-requested round — never merge_ready, never the
|
|
1495
|
+
* merge door; a green head proceeds with no wait added. */
|
|
1496
|
+
function settleChecks(
|
|
1497
|
+
s: UnitPipelineState,
|
|
1498
|
+
p: Extract<Phase, { at: "checks" }>,
|
|
1499
|
+
checks: RoundChecks | undefined,
|
|
1500
|
+
retried?: boolean,
|
|
1501
|
+
): Transition {
|
|
1502
|
+
const { round } = p;
|
|
1503
|
+
const wait = (over: Partial<Extract<Phase, { at: "checks-wait" }>>): Transition => ({
|
|
1504
|
+
state: {
|
|
1505
|
+
...s,
|
|
1506
|
+
phase: {
|
|
1507
|
+
at: "checks-wait",
|
|
1508
|
+
round,
|
|
1509
|
+
prNumber: p.prNumber,
|
|
1510
|
+
headSha: p.headSha,
|
|
1511
|
+
n: p.n,
|
|
1512
|
+
since: p.since,
|
|
1513
|
+
waitMs: p.waitMs,
|
|
1514
|
+
graced: p.graced,
|
|
1515
|
+
retried: p.retried,
|
|
1516
|
+
...over,
|
|
1517
|
+
},
|
|
1518
|
+
},
|
|
1519
|
+
notes: [],
|
|
1520
|
+
});
|
|
1521
|
+
// The retry ask. Dispatched: wait for the head to settle, then read again;
|
|
1522
|
+
// a second failure is the finding. NOT dispatched (`retried: false` — no
|
|
1523
|
+
// re-runnable run behind the checks, or GitHub refused): the one re-run is
|
|
1524
|
+
// spent all the same — the machine never asks twice — but nothing changes
|
|
1525
|
+
// at the head, so the checks are read again at once instead of waiting on a
|
|
1526
|
+
// settle event that never comes, and the failure becomes the finding.
|
|
1527
|
+
if (p.retry !== undefined) {
|
|
1528
|
+
if (retried === false) {
|
|
1529
|
+
const { retry: _retry, ...read } = p;
|
|
1530
|
+
return { state: { ...s, phase: { ...read, n: p.n + 1, retried: true } }, notes: [] };
|
|
1531
|
+
}
|
|
1532
|
+
return wait({ retried: true });
|
|
1533
|
+
}
|
|
1534
|
+
// GitHub unreadable answers as pending and is re-read at the chunk's end
|
|
1535
|
+
// (record 0055); a head still pending or unreadable at the ask's end
|
|
1536
|
+
// proceeds — the ending's facts read names what is still pending, and the
|
|
1537
|
+
// merge door (under `merge: runner`) is the guard that never merges over it.
|
|
1538
|
+
if (checks === undefined || checks.pending.length > 0) {
|
|
1539
|
+
if (s.clock - p.since >= p.waitMs) return approveOutcome(s, []);
|
|
1540
|
+
return wait({});
|
|
1541
|
+
}
|
|
1542
|
+
if (checks.failed.length > 0) {
|
|
1543
|
+
// The flake rule (record 0055): a suspected flake — a test timeout or
|
|
1544
|
+
// runner stall on a shard whose test files the pull request's changed
|
|
1545
|
+
// paths never touch — is re-run once before it becomes a finding. One
|
|
1546
|
+
// real failure among them makes the round's answer already known, so the
|
|
1547
|
+
// re-run is spent only when every failure is a suspect.
|
|
1548
|
+
if (!p.retried && checks.failed.every((f) => f.flakeSuspect === true))
|
|
1549
|
+
return {
|
|
1550
|
+
state: { ...s, phase: { ...p, n: p.n + 1, retry: checks.failed.map((f) => f.name) } },
|
|
1551
|
+
notes: [],
|
|
1552
|
+
};
|
|
1553
|
+
const findings = [...(s.findingsByRound[round.index] ?? []), ...checks.failed.map(checkFinding)];
|
|
1554
|
+
const next: UnitPipelineState = {
|
|
1555
|
+
...s,
|
|
1556
|
+
findingsByRound: { ...s.findingsByRound, [round.index]: findings },
|
|
1557
|
+
};
|
|
1558
|
+
// The failed checks get a round note of their own (record 0055) — never
|
|
1559
|
+
// the parser-mismatch gate — and the findings step runs as for any
|
|
1560
|
+
// changes-requested round: dispositions, a fix round, a re-review.
|
|
1561
|
+
const notes: CoordinatorNote[] = [roundNote(round, "checks_failed")];
|
|
1562
|
+
if (next.reviewRounds >= next.input.caps.maxRounds)
|
|
1563
|
+
return end(
|
|
1564
|
+
next,
|
|
1565
|
+
{ kind: "round_cap", maxRounds: next.input.caps.maxRounds, reviewRounds: next.reviewRounds },
|
|
1566
|
+
notes,
|
|
1567
|
+
);
|
|
1568
|
+
return enterRound(next, { index: round.index, kind: "findings" }, notes);
|
|
1569
|
+
}
|
|
1570
|
+
// No check reported at the head: one chunk of grace — the first check starts
|
|
1571
|
+
// within minutes where CI exists — then the round proceeds, so a repository
|
|
1572
|
+
// without CI costs one chunk per round and never idles (record 0055).
|
|
1573
|
+
if (checks.total === 0 && !p.graced) return wait({ graced: true });
|
|
1574
|
+
// Green (or none reported after the grace): today's path, no wait added.
|
|
1575
|
+
return approveOutcome(s, []);
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
/** An approve past the checks read: merge_ready for a person, the merge door
|
|
1579
|
+
* under `merge: runner` — the tail the round verdict guards (record 0055). */
|
|
1580
|
+
function approveOutcome(next: UnitPipelineState, notes: CoordinatorNote[]): Transition {
|
|
1581
|
+
{
|
|
1195
1582
|
const pr = next.pr!;
|
|
1196
1583
|
if (next.input.merge !== "runner")
|
|
1197
1584
|
return end(next, { kind: "merge_ready", pr, reviewRounds: next.reviewRounds }, notes);
|
|
@@ -1225,32 +1612,6 @@ function settleReview(
|
|
|
1225
1612
|
notes,
|
|
1226
1613
|
};
|
|
1227
1614
|
}
|
|
1228
|
-
// request_changes: the verdict settled (and posted) — now a stop
|
|
1229
|
-
// short-circuits the findings step, naming the review standing on the pull request.
|
|
1230
|
-
if (mode !== undefined)
|
|
1231
|
-
return end(
|
|
1232
|
-
next,
|
|
1233
|
-
{
|
|
1234
|
-
kind: "stopped",
|
|
1235
|
-
mode,
|
|
1236
|
-
round,
|
|
1237
|
-
reviewRounds: next.reviewRounds,
|
|
1238
|
-
...(facts.finalReply !== undefined ? { finalReply: facts.finalReply } : {}),
|
|
1239
|
-
...(facts.reviewPosted === true ? { postedReview: true } : {}),
|
|
1240
|
-
},
|
|
1241
|
-
notes,
|
|
1242
|
-
);
|
|
1243
|
-
if (next.reviewRounds >= next.input.caps.maxRounds)
|
|
1244
|
-
return end(
|
|
1245
|
-
next,
|
|
1246
|
-
{ kind: "round_cap", maxRounds: next.input.caps.maxRounds, reviewRounds: next.reviewRounds },
|
|
1247
|
-
notes,
|
|
1248
|
-
);
|
|
1249
|
-
// The findings step (agent-ship item 7): the review's findings dispatched into
|
|
1250
|
-
// the unit thread as a coding run, under the review round's index. A run live
|
|
1251
|
-
// there is a person's (this machine awaited its own child's end), so the
|
|
1252
|
-
// spawn answers busy and the step waits under the unit's clock like any round.
|
|
1253
|
-
return enterRound(next, { index: round.index, kind: "findings" }, notes);
|
|
1254
1615
|
}
|
|
1255
1616
|
|
|
1256
1617
|
/** The unit's ending when its pull request is found merged — by a person, or
|
|
@@ -1627,6 +1988,29 @@ export function applyReturn(s: UnitPipelineState, ret: StepReturn): Transition {
|
|
|
1627
1988
|
}
|
|
1628
1989
|
case "pr-check":
|
|
1629
1990
|
return settlePrCheck(clocked, p, (ret as Extract<StepReturn, { type: "pr-check" }>).pr);
|
|
1991
|
+
case "checks": {
|
|
1992
|
+
const r = ret as Extract<StepReturn, { type: "checks" }>;
|
|
1993
|
+
return settleChecks(clocked, p, r.checks, r.retried);
|
|
1994
|
+
}
|
|
1995
|
+
case "checks-wait":
|
|
1996
|
+
// The event fired or the chunk elapsed either way the head is read again.
|
|
1997
|
+
return {
|
|
1998
|
+
state: {
|
|
1999
|
+
...s,
|
|
2000
|
+
phase: {
|
|
2001
|
+
at: "checks",
|
|
2002
|
+
round: p.round,
|
|
2003
|
+
prNumber: p.prNumber,
|
|
2004
|
+
headSha: p.headSha,
|
|
2005
|
+
n: p.n + 1,
|
|
2006
|
+
since: p.since,
|
|
2007
|
+
waitMs: p.waitMs,
|
|
2008
|
+
graced: p.graced,
|
|
2009
|
+
retried: p.retried,
|
|
2010
|
+
},
|
|
2011
|
+
},
|
|
2012
|
+
notes: [],
|
|
2013
|
+
};
|
|
1630
2014
|
case "merge": {
|
|
1631
2015
|
const r = ret as Extract<StepReturn, { type: "merge" }>;
|
|
1632
2016
|
if (r.outcome === "merged")
|
|
@@ -1745,6 +2129,14 @@ export interface MergeReadyFacts {
|
|
|
1745
2129
|
merged?: { sha: string; mergedAt: string };
|
|
1746
2130
|
/** The check runs at the approved head as the merge door reads them (record 0055). */
|
|
1747
2131
|
checks?: CommitChecksFacts;
|
|
2132
|
+
/** GitHub's `mergeable_state` for the pull request — `dirty` is a conflict
|
|
2133
|
+
* with the base, and the report says so instead of "merge-ready", exactly
|
|
2134
|
+
* as the merge door refuses it before reading the checks (agent-ship item 9). */
|
|
2135
|
+
mergeableState?: string;
|
|
2136
|
+
/** The subjects of the head's commits that mark themselves as fix-ups
|
|
2137
|
+
* (git's autosquash prefixes `fixup!`, `squash!`, `amend!`): a head
|
|
2138
|
+
* carrying one is by its own words not in the ready state. */
|
|
2139
|
+
fixupCommits?: string[];
|
|
1748
2140
|
}
|
|
1749
2141
|
|
|
1750
2142
|
/** The check runs at one commit: how many, which still run, which failed. */
|
|
@@ -1755,9 +2147,19 @@ export interface CommitChecksFacts {
|
|
|
1755
2147
|
}
|
|
1756
2148
|
|
|
1757
2149
|
/** The merge-ready report's headline is a claim about the approved head
|
|
1758
|
-
* (record 0055): a
|
|
1759
|
-
*
|
|
1760
|
-
|
|
2150
|
+
* (record 0055, agent-ship item 9): a head that conflicts with its base is
|
|
2151
|
+
* never called merge-ready and neither is one carrying an unsquashed fix-up
|
|
2152
|
+
* commit — the ready state is read beside the checks, in the merge door's
|
|
2153
|
+
* order (the conflict first) — then a failed check is never called
|
|
2154
|
+
* merge-ready, a pending one is named, green is said, and without any fact
|
|
2155
|
+
* the line is unchanged. */
|
|
2156
|
+
function mergeReadyHeadline(rounds: string, url: string, base: string, facts: MergeReadyFacts | undefined): string {
|
|
2157
|
+
if (facts?.mergeableState === "dirty")
|
|
2158
|
+
return `⚠️ Approved but not merge-ready after ${rounds}: ${url} — the head conflicts with \`${base}\`: rebase onto \`${base}\`, push, and re-review. The approved work stands.`;
|
|
2159
|
+
const fixups = facts?.fixupCommits ?? [];
|
|
2160
|
+
if (fixups.length > 0)
|
|
2161
|
+
return `⚠️ Approved but not merge-ready after ${rounds}: ${url} — ${fixups.length} unsquashed fix-up commit${fixups.length === 1 ? "" : "s"} on the head (${fixups.join("; ")}): squash into the unit's commit, push, and re-review.`;
|
|
2162
|
+
const checks = facts?.checks;
|
|
1761
2163
|
if (checks === undefined) return `✅ Merge-ready after ${rounds}: ${url}`;
|
|
1762
2164
|
if (checks.failed.length > 0) {
|
|
1763
2165
|
const pending = checks.pending.length > 0 ? `; pending: ${checks.pending.join(", ")}` : "";
|
|
@@ -1769,9 +2171,21 @@ function mergeReadyHeadline(rounds: string, url: string, checks: CommitChecksFac
|
|
|
1769
2171
|
return `✅ Merge-ready after ${rounds}: ${url} — ${checks.total} check${checks.total === 1 ? "" : "s"} green at the approved head.`;
|
|
1770
2172
|
}
|
|
1771
2173
|
|
|
1772
|
-
export function renderUnitReport(
|
|
2174
|
+
export function renderUnitReport(
|
|
2175
|
+
s: UnitPipelineState,
|
|
2176
|
+
facts?: MergeReadyFacts,
|
|
2177
|
+
/** The level the report speaks at (routing-and-config item 28). The default
|
|
2178
|
+
* is the full report — the row's and the board's copy; the thread's copy is
|
|
2179
|
+
* rendered at the request's level, where the asides about the machinery
|
|
2180
|
+
* (the level in force, the grant, the write-up pointer, the budget split,
|
|
2181
|
+
* a segment boundary) are `verbose` and the outcome, the verdict, the
|
|
2182
|
+
* findings left below the gate, the declined ones and what to do next are
|
|
2183
|
+
* everyone's. */
|
|
2184
|
+
verbosity: Verbosity = "verbose",
|
|
2185
|
+
): string {
|
|
1773
2186
|
const e = s.ending;
|
|
1774
2187
|
if (!e) return "";
|
|
2188
|
+
const aside = (line: string | undefined) => (shows(verbosity, "verbose") ? line : undefined);
|
|
1775
2189
|
const rounds = `${e.reviewRounds} review round${e.reviewRounds === 1 ? "" : "s"}`;
|
|
1776
2190
|
const prUrl = s.pr?.url;
|
|
1777
2191
|
const prLine = prUrl ? ` PR: ${prUrl}` : "";
|
|
@@ -1809,11 +2223,13 @@ export function renderUnitReport(s: UnitPipelineState, facts?: MergeReadyFacts):
|
|
|
1809
2223
|
return [
|
|
1810
2224
|
`✅ Merged after ${rounds}: ${e.pr.url} (squash \`${e.sha.slice(0, 7)}\`) — merged by the plan runner under \`plan:merge\`: the review approved at this head and the guards were green.`,
|
|
1811
2225
|
verdictLine,
|
|
1812
|
-
levelLine,
|
|
1813
|
-
grantLine,
|
|
1814
|
-
|
|
2226
|
+
aside(levelLine),
|
|
2227
|
+
aside(grantLine),
|
|
2228
|
+
skippedLine,
|
|
1815
2229
|
declinedLine,
|
|
1816
|
-
]
|
|
2230
|
+
]
|
|
2231
|
+
.filter(Boolean)
|
|
2232
|
+
.join("\n");
|
|
1817
2233
|
case "already_landed":
|
|
1818
2234
|
// No compare link, no renewal line, no re-issue prompt: there was
|
|
1819
2235
|
// nothing to ship, so none of them has a question to answer.
|
|
@@ -1823,11 +2239,11 @@ export function renderUnitReport(s: UnitPipelineState, facts?: MergeReadyFacts):
|
|
|
1823
2239
|
// A merge that already happened outranks the checks: there is no head left to gate.
|
|
1824
2240
|
facts?.merged
|
|
1825
2241
|
? `✅ Merge-ready after ${rounds}: ${e.pr.url}`
|
|
1826
|
-
: mergeReadyHeadline(rounds, e.pr.url, facts
|
|
2242
|
+
: mergeReadyHeadline(rounds, e.pr.url, s.input.base, facts),
|
|
1827
2243
|
verdictLine,
|
|
1828
|
-
levelLine,
|
|
1829
|
-
grantLine,
|
|
1830
|
-
|
|
2244
|
+
aside(levelLine),
|
|
2245
|
+
aside(grantLine),
|
|
2246
|
+
skippedLine,
|
|
1831
2247
|
declinedLine,
|
|
1832
2248
|
// What the driver read at the approved head when it composed this
|
|
1833
2249
|
// ending (agent-ship item 9): a merge that already happened is named
|
|
@@ -1837,7 +2253,9 @@ export function renderUnitReport(s: UnitPipelineState, facts?: MergeReadyFacts):
|
|
|
1837
2253
|
: facts?.autoMergeEnabled
|
|
1838
2254
|
? "Auto-merge is on for this pull request: the approval merges it once checks pass."
|
|
1839
2255
|
: "Remaining gate: a person's merge — the runner merges only when the instance's `merge` field says runner, and ship never approves.",
|
|
1840
|
-
]
|
|
2256
|
+
]
|
|
2257
|
+
.filter(Boolean)
|
|
2258
|
+
.join("\n");
|
|
1841
2259
|
case "merge_refused":
|
|
1842
2260
|
// The approved work is on the branch, so the remedy is a person's hand
|
|
1843
2261
|
// merge, never a re-run: a seeded plan re-issued afterwards finds the
|
|
@@ -1860,23 +2278,25 @@ export function renderUnitReport(s: UnitPipelineState, facts?: MergeReadyFacts):
|
|
|
1860
2278
|
case "wall_clock_cap":
|
|
1861
2279
|
return join([
|
|
1862
2280
|
`🧢 Ship stopped at a cap: the remaining pipeline time (~${Math.max(0, Math.round(e.remainingMs / MIN))} min of the ${s.input.caps.maxMinutes}-minute budget) cannot hold another round${e.refused ? ` (the ${e.refused.round} round would get ${e.refused.minutes} min, under its floor of ${e.refused.floor})` : ""} — no approval after ${rounds}.${prLine}`,
|
|
1863
|
-
budgetSplitLine(e.spent, s.input.caps.maxMinutes),
|
|
2281
|
+
aside(budgetSplitLine(e.spent, s.input.caps.maxMinutes)),
|
|
1864
2282
|
splitReport(s),
|
|
1865
2283
|
reissue,
|
|
1866
2284
|
]);
|
|
1867
2285
|
case "review_pending":
|
|
1868
2286
|
return join([
|
|
1869
2287
|
`⏳ Review pending: the coding child shipped ${e.pr.url}${e.headSha !== undefined ? ` (head \`${e.headSha.slice(0, 7)}\`)` : ""} but the remaining pipeline time cannot hold the review round — the work stands, only the review is missing. The next attempt starts at the review round while the pull request still heads at the child's own last push.`,
|
|
1870
|
-
budgetSplitLine(e.spent, s.input.caps.maxMinutes),
|
|
2288
|
+
aside(budgetSplitLine(e.spent, s.input.caps.maxMinutes)),
|
|
1871
2289
|
reissue,
|
|
1872
2290
|
]);
|
|
1873
2291
|
case "stopped":
|
|
1874
2292
|
return join([
|
|
1875
2293
|
`${e.mode === "hard" ? "⛔" : "⏹"} Ship stopped by operator (${e.mode} stop) after ${rounds}.${prLine}`,
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
2294
|
+
aside(
|
|
2295
|
+
writeUpPointer(
|
|
2296
|
+
s,
|
|
2297
|
+
e.round.kind,
|
|
2298
|
+
e.round.kind === "review" ? s.reviewRunByRound[e.round.index] : s.lastCodingRunId,
|
|
2299
|
+
),
|
|
1880
2300
|
),
|
|
1881
2301
|
e.postedReview
|
|
1882
2302
|
? "ℹ️ A changes-requested review was posted this round before the stop — its findings stand on the PR."
|
|
@@ -1886,25 +2306,31 @@ export function renderUnitReport(s: UnitPipelineState, facts?: MergeReadyFacts):
|
|
|
1886
2306
|
case "aborted":
|
|
1887
2307
|
return join([
|
|
1888
2308
|
e.reason,
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
2309
|
+
aside(
|
|
2310
|
+
writeUpPointer(
|
|
2311
|
+
s,
|
|
2312
|
+
e.round?.kind ?? "coding",
|
|
2313
|
+
e.round?.kind === "review" ? s.reviewRunByRound[e.round.index] : s.lastCodingRunId,
|
|
2314
|
+
),
|
|
1893
2315
|
),
|
|
1894
2316
|
e.renewal !== undefined ? `🔁 Not renewed: ${e.renewal.line}.` : undefined,
|
|
1895
2317
|
`⚠️ Ship aborted after ${rounds}.`,
|
|
1896
2318
|
reissue,
|
|
1897
2319
|
]);
|
|
1898
2320
|
case "continued":
|
|
2321
|
+
// A segment boundary is the runner continuing — an acknowledgement, verbose
|
|
2322
|
+
// material (item 28); the thread's copy is empty at quiet, and the row keeps
|
|
2323
|
+
// no ending either way (decision 0046).
|
|
2324
|
+
if (!shows(verbosity, "verbose")) return "";
|
|
1899
2325
|
return join([
|
|
1900
2326
|
writeUpPointer(s, e.round.kind, e.runId),
|
|
1901
2327
|
`🔁 Segment ${e.segment - 1} ended at its lease with the unit unfinished — ${e.line}. Segment ${e.segment} opens in this thread${e.from !== undefined ? ` from \`${e.from.slice(0, 7)}\`` : ""} under a fresh ${s.input.caps.maxMinutes}-minute lease, with this segment's write-up as its request; ${e.renewalsLeft} renewal${e.renewalsLeft === 1 ? "" : "s"} remain${e.spendUsd !== null ? `, $${e.spendUsd.toFixed(2)} spent so far` : ""}.`,
|
|
1902
|
-
budgetSplitLine(e.spent, s.input.caps.maxMinutes),
|
|
2328
|
+
aside(budgetSplitLine(e.spent, s.input.caps.maxMinutes)),
|
|
1903
2329
|
]);
|
|
1904
2330
|
case "no_verdict":
|
|
1905
2331
|
return join([
|
|
1906
2332
|
`⚠️ Review round ${e.round.index} ended without a submitted verdict (budget, refusal, or stop) — ship never converts that into a request for changes, so no findings step ran.`,
|
|
1907
|
-
writeUpPointer(s, e.round.kind, s.reviewRunByRound[e.round.index]),
|
|
2333
|
+
aside(writeUpPointer(s, e.round.kind, s.reviewRunByRound[e.round.index])),
|
|
1908
2334
|
`⚠️ Ship aborted after ${rounds}.`,
|
|
1909
2335
|
reissue,
|
|
1910
2336
|
]);
|
|
@@ -1915,5 +2341,9 @@ export function renderUnitReport(s: UnitPipelineState, facts?: MergeReadyFacts):
|
|
|
1915
2341
|
`🚫 The ${presetOf(e.round.kind)} child of round ${e.round.index} was refused by the authorize stage (${e.refusal})${e.message ? `: ${e.message}` : ""} — every child is authorized as the requesting user, so the pipeline ends here.`,
|
|
1916
2342
|
reissue,
|
|
1917
2343
|
]);
|
|
2344
|
+
case "idle":
|
|
2345
|
+
// The old kind's sentence at this copy's level — the report is unchanged
|
|
2346
|
+
// by the idle at every verbosity (record 0051).
|
|
2347
|
+
return renderUnitReport({ ...s, ending: e.idled }, facts, verbosity);
|
|
1918
2348
|
}
|
|
1919
2349
|
}
|