@coreplane/switchboard 1.241.0 → 1.243.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 +45 -13
- package/dist/assets/deploy/cloudflare-memory/worker.ts +86 -29
- package/dist/assets/deploy/cloudflare-resident/worker.ts +298 -24
- package/dist/assets/deploy/cloudflare-sandbox/worker.ts +38 -5
- package/dist/assets/package-lock.json +3 -3
- package/dist/assets/package.json +1 -1
- package/dist/assets/project.json +5 -5
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/agents/registry.ts +61 -48
- package/dist/assets/src/config/profile.ts +68 -3
- package/dist/assets/src/core/authz/actor.ts +12 -9
- package/dist/assets/src/core/authz/authorize.ts +17 -4
- package/dist/assets/src/core/authz/policy.ts +4 -0
- package/dist/assets/src/core/authz/types.ts +12 -0
- package/dist/assets/src/core/budgets.ts +313 -0
- package/dist/assets/src/core/coordinator/contract.ts +3 -1
- package/dist/assets/src/core/coordinator/driver.ts +0 -10
- package/dist/assets/src/core/costs.ts +21 -80
- package/dist/assets/src/core/harness/scope.ts +21 -0
- package/dist/assets/src/core/modelPricing.ts +212 -0
- package/dist/assets/src/core/prDescriptionTypes.ts +29 -24
- package/dist/assets/src/core/reviewVerdict.ts +7 -0
- package/dist/assets/src/core/runEvents.ts +48 -5
- package/dist/assets/src/core/runFriction.ts +2 -1
- package/dist/assets/src/core/runLedger/types.ts +2 -0
- package/dist/assets/src/core/runRecord.ts +51 -0
- package/dist/assets/src/core/runUsage.ts +158 -47
- package/dist/assets/src/core/schedules.ts +3 -0
- package/dist/assets/src/core/ship/contract.ts +4 -2
- package/dist/assets/src/core/ship/coordinator.ts +101 -84
- package/dist/assets/src/core/ship/handoff.ts +9 -0
- package/dist/assets/src/execution/bashTimeout.ts +8 -5
- package/dist/assets/src/execution/residentAutoRebuild.ts +4 -2
- package/dist/assets/src/execution/residentHead.ts +23 -10
- package/dist/assets/src/execution/residentInfraStreak.ts +89 -0
- package/dist/assets/src/execution/residentRefresh.ts +43 -4
- package/dist/assets/src/execution/residentSteps.ts +1 -0
- package/dist/assets/src/execution/sandboxErrors.ts +6 -0
- package/dist/assets/src/execution/seedPlan.ts +17 -0
- package/dist/assets/web/dist/.vite/manifest.json +443 -446
- package/dist/assets/web/dist/assets/{AppShell-DEy5jRuS.js → AppShell-IDMGG6yi.js} +1 -1
- package/dist/assets/web/dist/assets/CostsPage-CwXOmkeQ.js +2 -0
- package/dist/assets/web/dist/assets/{DeliveryPage-BGpPr5xr.js → DeliveryPage-n1tz5I_v.js} +1 -1
- package/dist/assets/web/dist/assets/HomePage-PRxjQiGG.js +2 -0
- package/dist/assets/web/dist/assets/{NotFoundPage-DtmQM2gD.js → NotFoundPage-C97EhJcQ.js} +1 -1
- package/dist/assets/web/dist/assets/PendingTurnRow-BT9RhFZ7.js +1 -0
- package/dist/assets/web/dist/assets/{ResidentDetailPage-C-48fxat.js → ResidentDetailPage-DACalNVF.js} +1 -1
- package/dist/assets/web/dist/assets/ResidentsIndexPage-SHPdu6uZ.js +1 -0
- package/dist/assets/web/dist/assets/RunFoldRow-0SdOmOr5.js +1 -0
- package/dist/assets/web/dist/assets/RunRoutePage-BaFS2p8I.js +9 -0
- package/dist/assets/web/dist/assets/RunsIndexPage-DYI-iALj.js +1 -0
- package/dist/assets/web/dist/assets/{RunsTabs-CtNGNnhN.js → RunsTabs-BUfdk0lH.js} +1 -1
- package/dist/assets/web/dist/assets/ScheduledPage-DJ8HiCPt.js +1 -0
- package/dist/assets/web/dist/assets/SettingsPage-DLiN5IgY.js +1 -0
- package/dist/assets/web/dist/assets/{StatusDot-DPWE1JmO.js → StatusDot-Dw0T1M-P.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-DKhSRH9t.js → Tooltip-BbLuIAiS.js} +1 -1
- package/dist/assets/web/dist/assets/UnitRoutePage-DicUG96U.js +1 -0
- package/dist/assets/web/dist/assets/{angular-html-DgSK1qvr.js → angular-html-oBNfPJR0.js} +1 -1
- package/dist/assets/web/dist/assets/{angular-ts-D3gNdiSG.js → angular-ts-BvNwsyWA.js} +1 -1
- package/dist/assets/web/dist/assets/{apl-CkHCYM8I.js → apl-CNUdRlYf.js} +1 -1
- package/dist/assets/web/dist/assets/{astro-DLm45axt.js → astro-Zb0NriSe.js} +1 -1
- package/dist/assets/web/dist/assets/{blade-BKa-VE-c.js → blade-qPRVheqq.js} +1 -1
- package/dist/assets/web/dist/assets/{c-C8NCNWai.js → c-D8Awx4YO.js} +1 -1
- package/dist/assets/web/dist/assets/{chapel-DqDQ7IKH.js → chapel-Bt72Mhsx.js} +1 -1
- package/dist/assets/web/dist/assets/{cobol-j4vCD6AJ.js → cobol-BOBacexg.js} +1 -1
- package/dist/assets/web/dist/assets/{coffee-C6oDVH-0.js → coffee-E4u0liHW.js} +1 -1
- package/dist/assets/web/dist/assets/{cpp-Bd3A1Rtc.js → cpp-q2sLNlul.js} +1 -1
- package/dist/assets/web/dist/assets/{crystal-CUOgOw0d.js → crystal-DyWqUnlb.js} +1 -1
- package/dist/assets/web/dist/assets/{css-RYljyv7G.js → css-CQY0hFsD.js} +1 -1
- package/dist/assets/web/dist/assets/{dist-BnJXKHCY.js → dist-twkFmSUY.js} +2 -2
- package/dist/assets/web/dist/assets/durationTone-BobbycC-.js +1 -0
- package/dist/assets/web/dist/assets/{edge-BJhNdQDc.js → edge-C1MwhJkX.js} +1 -1
- package/dist/assets/web/dist/assets/{elixir-g1AWOAfT.js → elixir-Bb3YbHfn.js} +1 -1
- package/dist/assets/web/dist/assets/{elm-CER5e4Dz.js → elm-DAN9IGQw.js} +1 -1
- package/dist/assets/web/dist/assets/{erb-D5aOmMQf.js → erb-BEB8Xlsj.js} +1 -1
- package/dist/assets/web/dist/assets/format-BldUwl_R.js +1 -0
- package/dist/assets/web/dist/assets/{git-rebase-D6rfV8jp.js → git-rebase-tqpjRfxO.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-js-BvDH3-mC.js → glimmer-js-Ccbo65zR.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-ts-DChtNs9V.js → glimmer-ts-B6WBVMpE.js} +1 -1
- package/dist/assets/web/dist/assets/{glsl-BHlsWrxf.js → glsl-tRec3Fcu.js} +1 -1
- package/dist/assets/web/dist/assets/{graphql-BsaBHb53.js → graphql-P8kbxT4F.js} +1 -1
- package/dist/assets/web/dist/assets/{hack-0lbDosCd.js → hack-H9Zhkagy.js} +1 -1
- package/dist/assets/web/dist/assets/{haml-BKtco2vr.js → haml-DtEnpn7Z.js} +1 -1
- package/dist/assets/web/dist/assets/{handlebars-BE_cj04z.js → handlebars-DkgPfoAz.js} +1 -1
- package/dist/assets/web/dist/assets/{html-BNcr8EVd.js → html-D30RXpIs.js} +1 -1
- package/dist/assets/web/dist/assets/{html-derivative-BsJt2Kei.js → html-derivative-DwozLrEx.js} +1 -1
- package/dist/assets/web/dist/assets/{http-BR5P8ER_.js → http-DXuzBAPm.js} +1 -1
- package/dist/assets/web/dist/assets/{hurl-CtHalpWS.js → hurl-d1UUJIt_.js} +1 -1
- package/dist/assets/web/dist/assets/indexRow-B_s5tKyq.js +1 -0
- package/dist/assets/web/dist/assets/{java-BXVdhN11.js → java-DL0gWf34.js} +1 -1
- package/dist/assets/web/dist/assets/{javascript-CyoShgNQ.js → javascript-Cl7vavnS.js} +1 -1
- package/dist/assets/web/dist/assets/{jinja-BQYZzHex.js → jinja-CfQOWMX9.js} +1 -1
- package/dist/assets/web/dist/assets/{jison-DqrUa1Eq.js → jison-KdYirlqm.js} +1 -1
- package/dist/assets/web/dist/assets/{json-CQiTF9Jj.js → json-C9cDQ-Qj.js} +1 -1
- package/dist/assets/web/dist/assets/{jsx-Y16MkCYQ.js → jsx-CRx5NItd.js} +1 -1
- package/dist/assets/web/dist/assets/{julia-zn16YONe.js → julia-CmsQsZQl.js} +1 -1
- package/dist/assets/web/dist/assets/{just-DcVeN_MN.js → just-CsM3Q8TE.js} +1 -1
- package/dist/assets/web/dist/assets/{latex-BNNoF-WP.js → latex-BXCh5YRX.js} +1 -1
- package/dist/assets/web/dist/assets/{liquid-DAi6qHzn.js → liquid-BF2vwK8p.js} +1 -1
- package/dist/assets/web/dist/assets/{lua-BswS0axw.js → lua-DcMBATrl.js} +1 -1
- package/dist/assets/web/dist/assets/main-B4kEF3Sg.css +1 -0
- package/dist/assets/web/dist/assets/{main-B3is4sk6.js → main-d-w-tIKt.js} +2 -2
- package/dist/assets/web/dist/assets/{marko-C-4hkmQZ.js → marko-88MndKvG.js} +1 -1
- package/dist/assets/web/dist/assets/{mdc-0ZaHUkS7.js → mdc-DJ4kVAd8.js} +1 -1
- package/dist/assets/web/dist/assets/{nginx-DBDg5tOR.js → nginx-CP6mRgtV.js} +1 -1
- package/dist/assets/web/dist/assets/{nim-BVMz49Yw.js → nim-QQfj3fpF.js} +1 -1
- package/dist/assets/web/dist/assets/{org-1hO_K6ya.js → org-Bke3eBzc.js} +1 -1
- package/dist/assets/web/dist/assets/{perl-CVRQLvVk.js → perl-Bkh0N7Kz.js} +1 -1
- package/dist/assets/web/dist/assets/{php-K9nCrB9n.js → php-Dnv1Piya.js} +1 -1
- package/dist/assets/web/dist/assets/{pug-CTxOcmO3.js → pug-D6peFFI7.js} +1 -1
- package/dist/assets/web/dist/assets/{qml-CmCkfG5q.js → qml-D8PEs-C-.js} +1 -1
- package/dist/assets/web/dist/assets/{r-pP47Xn1X.js → r-B1EL9b_j.js} +1 -1
- package/dist/assets/web/dist/assets/{razor-BpW6r3tC.js → razor-BsMTIh2b.js} +1 -1
- package/dist/assets/web/dist/assets/{regexp-CINgdY4N.js → regexp-HhvC8spD.js} +1 -1
- package/dist/assets/web/dist/assets/{rst-BZIUEq2Q.js → rst-D5paAxpg.js} +1 -1
- package/dist/assets/web/dist/assets/{ruby-nzAOxz6r.js → ruby-BrQwhLrl.js} +1 -1
- package/dist/assets/web/dist/assets/{sas-QIS1bFth.js → sas-afot2B1o.js} +1 -1
- package/dist/assets/web/dist/assets/{scss-BuhBBVNt.js → scss-Efm-mwuG.js} +1 -1
- package/dist/assets/web/dist/assets/{shellscript-CQc1vXbk.js → shellscript-BK0Vv5fT.js} +1 -1
- package/dist/assets/web/dist/assets/{shellsession-CoubCAUv.js → shellsession-BGqlMC7N.js} +1 -1
- package/dist/assets/web/dist/assets/{soy-CPRzlder.js → soy-M0b4UwGM.js} +1 -1
- package/dist/assets/web/dist/assets/{sql-W9krb8-9.js → sql-sxe6IE9j.js} +1 -1
- package/dist/assets/web/dist/assets/sseReplay-C9m_EB8J.js +9 -0
- package/dist/assets/web/dist/assets/{stata-CuISJEC0.js → stata-nPF_ddLP.js} +1 -1
- package/dist/assets/web/dist/assets/{surrealql-CSet7584.js → surrealql-D_GrC6u7.js} +1 -1
- package/dist/assets/web/dist/assets/{svelte-ChXTSYwK.js → svelte-DwL1AtNP.js} +1 -1
- package/dist/assets/web/dist/assets/{templ-Bm55v62k.js → templ-7s7LTDkc.js} +1 -1
- package/dist/assets/web/dist/assets/{tex-CICMX8Gj.js → tex-Bx-5fMxe.js} +1 -1
- package/dist/assets/web/dist/assets/{ts-tags-CA1UzWyB.js → ts-tags-DUMJnke_.js} +1 -1
- package/dist/assets/web/dist/assets/{tsx-Dy04HNbv.js → tsx-BN8biPDe.js} +1 -1
- package/dist/assets/web/dist/assets/{twig-BBHsVnVD.js → twig-8nIu84TN.js} +1 -1
- package/dist/assets/web/dist/assets/{typescript-Dvc-wVBT.js → typescript-BooSPq_S.js} +1 -1
- package/dist/assets/web/dist/assets/{typst-Dvlqx3_q.js → typst-CTBiBsem.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-zWi1MNU2.js → vue-C6Ft4Lea.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-html-D4YxT3An.js → vue-html-Ba36dD5D.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-vine-0YE7uQlH.js → vue-vine-NkFexVo2.js} +1 -1
- package/dist/assets/web/dist/assets/{xml-gm-iksZB.js → xml-C_THnHXZ.js} +1 -1
- package/dist/assets/web/dist/assets/{xsl-D_W5BcrY.js → xsl-D8G5xqjY.js} +1 -1
- package/dist/assets/web/dist/assets/{yaml-CHmZ21wZ.js → yaml-jAMIJzge.js} +1 -1
- package/dist/cli.js +4121 -1902
- package/package.json +1 -1
- package/dist/assets/web/dist/assets/CostsPage-BuHD-Bv7.js +0 -2
- package/dist/assets/web/dist/assets/HomePage-dKBvWh-E.js +0 -2
- package/dist/assets/web/dist/assets/PendingTurnRow-CrEAWx5b.js +0 -1
- package/dist/assets/web/dist/assets/ResidentsIndexPage-CBBIjPOa.js +0 -1
- package/dist/assets/web/dist/assets/RunFoldRow-B6vxjcBI.js +0 -1
- package/dist/assets/web/dist/assets/RunRoutePage-CQjd3Cz-.js +0 -6
- package/dist/assets/web/dist/assets/RunsIndexPage-Cy9G6D0p.js +0 -1
- package/dist/assets/web/dist/assets/ScheduledPage-Ci1Ygqnc.js +0 -1
- package/dist/assets/web/dist/assets/SettingsPage-DxmJvAmT.js +0 -1
- package/dist/assets/web/dist/assets/SlackMark-VDNs7Vjh.js +0 -1
- package/dist/assets/web/dist/assets/UnitRoutePage-DEN5bSQf.js +0 -1
- package/dist/assets/web/dist/assets/durationTone-DXG-3R7_.js +0 -1
- package/dist/assets/web/dist/assets/indexRow-BD1VT8o8.js +0 -1
- package/dist/assets/web/dist/assets/localIso-L06jV29p.js +0 -1
- package/dist/assets/web/dist/assets/main-DP_zSemY.css +0 -1
- package/dist/assets/web/dist/assets/sseReplay-yji8a1wM.js +0 -9
|
@@ -196,6 +196,15 @@ function entryLines(h: Handoff): Array<{ list: ListKey; bullet: string; row: str
|
|
|
196
196
|
];
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
/** Each entry as one line, list by list — a deviation as `Deviation: from → to
|
|
200
|
+
* — why`, a follow-up as `what — where`, an unproven criterion as `Unproven:
|
|
201
|
+
* criterion — why` — the same words the ledger rows carry, for a reader that
|
|
202
|
+
* wants the handoff as plain lines (the thread's artifacts block). Empty for
|
|
203
|
+
* an empty handoff. */
|
|
204
|
+
export function handoffLines(h: Handoff): string[] {
|
|
205
|
+
return entryLines(h).map((e) => e.row);
|
|
206
|
+
}
|
|
207
|
+
|
|
199
208
|
function prLink(pr: HandoffRenderContext["pr"]): string | undefined {
|
|
200
209
|
return pr ? `[#${pr.number}](${pr.url})` : undefined;
|
|
201
210
|
}
|
|
@@ -2,28 +2,31 @@
|
|
|
2
2
|
// one set of bounds, shared by the tool layer, every executor, and both deploy
|
|
3
3
|
// Workers (which import from src/execution like shellQuote/residentDetach).
|
|
4
4
|
// Deliberately free of node: imports so wrangler can bundle it into Workers.
|
|
5
|
+
// The numbers are rows of src/core/budgets.ts (docs/decisions/0046); this file
|
|
6
|
+
// keeps their names for the readers that learned them here.
|
|
7
|
+
import { ALLOWANCES, BASH_COMMAND, MINUTE_MS } from "../core/budgets.js";
|
|
5
8
|
|
|
6
9
|
/** Default per-command budget when the caller passes no timeoutMs. */
|
|
7
|
-
export const BASH_TIMEOUT_MS =
|
|
10
|
+
export const BASH_TIMEOUT_MS = BASH_COMMAND.defaultMinutes * MINUTE_MS;
|
|
8
11
|
|
|
9
12
|
/** Floor: anything lower is a typo or an attack, not a budget. */
|
|
10
|
-
export const BASH_TIMEOUT_MIN_MS =
|
|
13
|
+
export const BASH_TIMEOUT_MIN_MS = BASH_COMMAND.minMs;
|
|
11
14
|
|
|
12
15
|
/** Hard ceiling a caller can raise the budget to. Not by itself a guarantee
|
|
13
16
|
* against one command eating a run — 20 minutes is 80% of a 25-minute review
|
|
14
17
|
* — which is what `bashBudgetWithinRun` below is for. */
|
|
15
|
-
export const BASH_TIMEOUT_MAX_MS =
|
|
18
|
+
export const BASH_TIMEOUT_MAX_MS = BASH_COMMAND.maxMinutes * MINUTE_MS;
|
|
16
19
|
|
|
17
20
|
/** Wall clock a run keeps back from its last command for the write-up: the
|
|
18
21
|
* runner's deadline forces a final answer, and a command still running at
|
|
19
22
|
* that moment would have been wasted anyway. */
|
|
20
|
-
export const RUN_DEADLINE_RESERVE_MS =
|
|
23
|
+
export const RUN_DEADLINE_RESERVE_MS = ALLOWANCES.commandWriteUp * MINUTE_MS;
|
|
21
24
|
|
|
22
25
|
/** Margin the remote exec clients add to their HTTP wait over the command
|
|
23
26
|
* budget, so the server's own timeout answer (a streamed exit 124) wins the
|
|
24
27
|
* race against the client's transport deadline instead of both firing at the
|
|
25
28
|
* same instant. */
|
|
26
|
-
export const EXEC_CALL_MARGIN_MS =
|
|
29
|
+
export const EXEC_CALL_MARGIN_MS = ALLOWANCES.execCall * MINUTE_MS;
|
|
27
30
|
|
|
28
31
|
/** The documented clamp rule, applied identically bot-side and server-side
|
|
29
32
|
* (a server never trusts the client's number): a finite number is truncated
|
|
@@ -9,8 +9,10 @@
|
|
|
9
9
|
* cycle, and these say the snapshot (or the container that would restore it)
|
|
10
10
|
* cannot be used — never a provision failure, which would loop against the
|
|
11
11
|
* same broken build. `runtime-unreachable` is item 64's last rung: a recreated
|
|
12
|
-
* container that did not answer either
|
|
13
|
-
|
|
12
|
+
* container that did not answer either; `infra-streak` is item 67's: a
|
|
13
|
+
* recreated container whose cycles kept failing in the resident's own steps. */
|
|
14
|
+
export const REHYDRATION_FAILURE_RE =
|
|
15
|
+
/^(r2-restore-failed|snapshot-stamp-mismatch|no-snapshot|runtime-unreachable|infra-streak)/;
|
|
14
16
|
|
|
15
17
|
/** How many auto-rebuilds one resident gets inside one window. A rebuild is a
|
|
16
18
|
* clone, an install and a build (minutes to half an hour) that holds the cap
|
|
@@ -67,10 +67,9 @@ export type FetchReason = "missing-ref" | "stale-tip" | "returnable-ref";
|
|
|
67
67
|
* lost and a ref a person named that the thread never pushed has no way
|
|
68
68
|
* back, so neither pays this fetch: the refresh cycle is their freshness.
|
|
69
69
|
* - otherwise null.
|
|
70
|
-
* A fetch that STILL leaves the tip elsewhere (a push racing this attach,
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* review of it may do. */
|
|
70
|
+
* A fetch that STILL leaves the tip elsewhere (a push racing this attach, a
|
|
71
|
+
* force-push, or a fetch that failed) is `attachTarget`'s concern: the attach
|
|
72
|
+
* is refused `stale-tip` rather than run at a commit nobody asked for. */
|
|
74
73
|
export function mirrorFetchReason(input: {
|
|
75
74
|
refExists: boolean;
|
|
76
75
|
mirrorSha?: string;
|
|
@@ -85,8 +84,16 @@ export function mirrorFetchReason(input: {
|
|
|
85
84
|
|
|
86
85
|
/** What the attach checks out, once the mirror is as fresh as it will get. */
|
|
87
86
|
export type AttachTarget =
|
|
88
|
-
/** The bound ref is in the mirror
|
|
87
|
+
/** The bound ref is in the mirror, and its tip is the commit the caller
|
|
88
|
+
* named (or the caller named none): clone its tip, as always. */
|
|
89
89
|
| { kind: "ref" }
|
|
90
|
+
/** The bound ref is in the mirror but its tip is NOT the commit the caller
|
|
91
|
+
* named, even after the fetch — the mirror is behind it (the fetch failed
|
|
92
|
+
* or was skipped) or ahead of it (a push raced the attach). Refused: a run
|
|
93
|
+
* executes at the sha it asked for, or not on this resident; the caller
|
|
94
|
+
* falls back cold at the requested commit. `tip` is null when the tip
|
|
95
|
+
* could not be read — never assumed fresh. */
|
|
96
|
+
| { kind: "stale-tip"; tip: string | null; want: string }
|
|
90
97
|
/** The ref is gone but the commit the caller expects is in the mirror — a
|
|
91
98
|
* merged PR's branch was deleted while `refs/pull/N/head` (a `--mirror`
|
|
92
99
|
* clone carries every ref) still holds its head: check that commit out,
|
|
@@ -96,16 +103,22 @@ export type AttachTarget =
|
|
|
96
103
|
/** Neither: the attach is refused as `unknown-ref`. */
|
|
97
104
|
| { kind: "unknown-ref" };
|
|
98
105
|
|
|
99
|
-
/** The attach target after the fetch (item 51).
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
106
|
+
/** The attach target after the fetch (item 51). A ref that exists is cloned
|
|
107
|
+
* at its tip when the caller named no commit or the tip is that commit;
|
|
108
|
+
* a tip that is any other commit is `stale-tip`, refused. A detached tree is
|
|
109
|
+
* only for a ref that is gone; a caller that named no commit, or whose commit
|
|
110
|
+
* the mirror does not hold either, gets the refusal it always got. */
|
|
103
111
|
export function attachTarget(input: {
|
|
104
112
|
refExists: boolean;
|
|
105
113
|
wantSha: string | null;
|
|
106
114
|
commitInMirror: boolean;
|
|
115
|
+
/** The ref's tip in the mirror after the fetch; null or absent when it could not be read. */
|
|
116
|
+
tipSha?: string | null;
|
|
107
117
|
}): AttachTarget {
|
|
108
|
-
if (input.refExists)
|
|
118
|
+
if (input.refExists) {
|
|
119
|
+
if (input.wantSha === null || input.tipSha === input.wantSha) return { kind: "ref" };
|
|
120
|
+
return { kind: "stale-tip", tip: input.tipSha ?? null, want: input.wantSha };
|
|
121
|
+
}
|
|
109
122
|
if (input.wantSha !== null && input.commitInMirror) return { kind: "sha", sha: input.wantSha };
|
|
110
123
|
return { kind: "unknown-ref" };
|
|
111
124
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// A refresh cycle that keeps failing in the resident's OWN steps heals itself
|
|
2
|
+
// (docs/reference/specs/resident-repos.md item 67). Pure: the Worker's
|
|
3
|
+
// `refreshFailed` bumps the row and acts on the rung; the cycle's gate asks
|
|
4
|
+
// `parksOnRepeat` before it counts a degraded reason toward the park streak.
|
|
5
|
+
//
|
|
6
|
+
// The split this module draws: a step that runs the repository's own command
|
|
7
|
+
// (the onboard-time command table — install, build, test) failing is evidence
|
|
8
|
+
// about the repository, and the right response is to park and wait for the
|
|
9
|
+
// head to be fixed, which is what the park streak does. Every other step is
|
|
10
|
+
// the resident's machinery — git against the mirror, the probes, the markers,
|
|
11
|
+
// the restores — and a failure there that repeats is the container or the
|
|
12
|
+
// disk gone wrong, which a rebuild does fix. Item 64 already climbs this
|
|
13
|
+
// ladder for one signature (the control port that never answers); this is
|
|
14
|
+
// the same ladder for every other resident-step failure.
|
|
15
|
+
|
|
16
|
+
import { STALE_SWEEP_SUFFIX } from "./residentSteps.js";
|
|
17
|
+
|
|
18
|
+
/** The steps that run the repository's own commands. */
|
|
19
|
+
export const REPO_COMMAND_STEPS: ReadonlySet<string> = new Set(["deps-install", "build", "test"]);
|
|
20
|
+
|
|
21
|
+
/** Whether a failed step ran the repository's command (evidence about the
|
|
22
|
+
* repo) rather than the resident's own machinery. The stale-process sweep
|
|
23
|
+
* that precedes a repo step is ours. */
|
|
24
|
+
export function isRepoCommandStep(step: string): boolean {
|
|
25
|
+
if (step.endsWith(STALE_SWEEP_SUFFIX)) return false;
|
|
26
|
+
return REPO_COMMAND_STEPS.has(step);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The step named by a `<step>-failed:` reason (`classifyRefreshFailure`'s
|
|
30
|
+
* plain-failure shape), or null for every other reason. */
|
|
31
|
+
export function stepOfFailedReason(reason: string): string | null {
|
|
32
|
+
const m = /^([a-z][a-z0-9-]*)-failed:/.exec(reason);
|
|
33
|
+
return m ? m[1] : null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Whether a repeated degraded reason should PARK the resident (the 6-hour
|
|
37
|
+
* cadence, `DEGRADED_PARK_AFTER_CYCLES`): only evidence about the repository
|
|
38
|
+
* or about GitHub — a repo-command step's failure, or a reason that names no
|
|
39
|
+
* step at all (`github-unreachable`). A resident-step failure never parks:
|
|
40
|
+
* the ladder below is its escalation, and parking would only slow it. */
|
|
41
|
+
export function parksOnRepeat(reason: string): boolean {
|
|
42
|
+
const step = stepOfFailedReason(reason);
|
|
43
|
+
return step === null || isRepoCommandStep(step);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The ladder's rungs: consecutive cycles failed in resident steps. */
|
|
47
|
+
export const INFRA_STREAK_RECREATE_AT = 3;
|
|
48
|
+
export const INFRA_STREAK_DOWN_AT = 5;
|
|
49
|
+
|
|
50
|
+
export type InfraStreakRung = "count" | "recreate" | "down";
|
|
51
|
+
|
|
52
|
+
/** Count 1–2: record and let the next cycle try. Count 3: destroy the
|
|
53
|
+
* container, snapshots kept — the next cycle restores from the snapshot onto
|
|
54
|
+
* a fresh disk (item 64's rung 3). Count 4: the recreated container's one
|
|
55
|
+
* cycle of its own. Count ≥ 5: a fresh container failed the same way; `down`
|
|
56
|
+
* with a rehydration-flavored reason, so item 36's transition rebuild is the
|
|
57
|
+
* exit. */
|
|
58
|
+
export function infraStreakRung(count: number): InfraStreakRung {
|
|
59
|
+
if (!Number.isFinite(count) || count < 1) {
|
|
60
|
+
throw new RangeError(`infra-streak rung needs a count of at least 1, got ${count}`);
|
|
61
|
+
}
|
|
62
|
+
if (count >= INFRA_STREAK_DOWN_AT) return "down";
|
|
63
|
+
if (count === INFRA_STREAK_RECREATE_AT) return "recreate";
|
|
64
|
+
return "count";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The persisted row (`resident:infraStreak`): the last failing step, the
|
|
68
|
+
* consecutive count and the span. */
|
|
69
|
+
export interface InfraStreakRow {
|
|
70
|
+
step: string;
|
|
71
|
+
count: number;
|
|
72
|
+
firstAt: string;
|
|
73
|
+
lastAt: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const INFRA_STREAK_ACTION: Readonly<Record<Exclude<InfraStreakRung, "count">, string>> = {
|
|
77
|
+
recreate: "the container was destroyed, snapshots kept; the next cycle restores from the snapshot",
|
|
78
|
+
down: "a recreated container failed the same way; rebuilding",
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/** The reason a rung records: `infra-streak:` so the gate, the serviceability
|
|
82
|
+
* check and item 36's eligibility all read it as the resident's, never the repo's. */
|
|
83
|
+
export function infraStreakReason(row: InfraStreakRow, rung: Exclude<InfraStreakRung, "count">): string {
|
|
84
|
+
return `infra-streak: ${row.count} consecutive cycles failed in the resident's own steps (last: ${row.step}, since ${row.firstAt}) — ${INFRA_STREAK_ACTION[rung]}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function isInfraStreakReason(reason: string): boolean {
|
|
88
|
+
return /^infra-streak: /.test(reason);
|
|
89
|
+
}
|
|
@@ -132,6 +132,10 @@ export interface RefreshFailure {
|
|
|
132
132
|
* with the attempt count (the ladder below decides what the resident does
|
|
133
133
|
* about it); real failures keep `<step>-failed:`. */
|
|
134
134
|
reason: string;
|
|
135
|
+
/** The step that failed, as the caller named it (`refresh` for a failure
|
|
136
|
+
* between steps): item 67 reads whether it ran the repository's own command
|
|
137
|
+
* or the resident's machinery. */
|
|
138
|
+
step: string;
|
|
135
139
|
interrupted: boolean;
|
|
136
140
|
diskFull: boolean;
|
|
137
141
|
runtimeUnreachable: boolean;
|
|
@@ -205,9 +209,34 @@ const INTERRUPTION_SIGNATURE = /\bexit 143\b|Session terminated|SIGTERM|^restore
|
|
|
205
209
|
* exec path and the refresh classifier agree on one wording list (a container
|
|
206
210
|
* stop mid-snapshot produces "Process supervisor is closed"; classified as the
|
|
207
211
|
* repo's own `snapshot-failed` it would end the instance `degraded` instead of
|
|
208
|
-
* being retried into the same step).
|
|
209
|
-
|
|
210
|
-
|
|
212
|
+
* being retried into the same step).
|
|
213
|
+
*
|
|
214
|
+
* The list is the union of two a reader tells apart, because the resident's
|
|
215
|
+
* `/exec` answer treats them differently (resident-repos item 43): the SDK's
|
|
216
|
+
* words that vouch the runtime MOVED — a new incarnation serves the thread,
|
|
217
|
+
* so the container the command was aimed at is gone whatever else is known
|
|
218
|
+
* — and the platform's words for a container that is merely DOWN, which say
|
|
219
|
+
* nothing about a replacement: a stopped container answers "Process
|
|
220
|
+
* supervisor is closed" to a spawn, and the workerd binding answers "The
|
|
221
|
+
* container is not running" whether the container is asleep, starting, or
|
|
222
|
+
* gone under a roll. For the refresh classifier and `isRuntimeReplacement`
|
|
223
|
+
* the union is right: a step interrupted either way is retried onto the
|
|
224
|
+
* container that comes back. */
|
|
225
|
+
export const RUNTIME_MOVED_WORDING =
|
|
226
|
+
/previous runtime incarnation|interrupted because the runtime changed|runtime identity is no longer active|sandbox lifetime is no longer current|platform was updating the sandbox runtime|no longer identifies pid/i;
|
|
227
|
+
/** The platform's words for a container that is down under a command (see
|
|
228
|
+
* `RUNTIME_MOVED_WORDING`): the spawn refusal of a stopped container and the
|
|
229
|
+
* binding's not-running refusal, anchored to the full phrase so a genuine
|
|
230
|
+
* crash ("container exited with unexpected exit code") and the readiness
|
|
231
|
+
* probe ("the container is not listening") never match. The harness's
|
|
232
|
+
* container seam reads the same two as a container down under its one more
|
|
233
|
+
* command — a wait, never a judgement (harness.md item 6). */
|
|
234
|
+
export const STOPPED_CONTAINER_WORDING =
|
|
235
|
+
/process supervisor is closed|container is not running, consider calling start/i;
|
|
236
|
+
export const RUNTIME_REPLACEMENT_WORDING = new RegExp(
|
|
237
|
+
`${RUNTIME_MOVED_WORDING.source}|${STOPPED_CONTAINER_WORDING.source}`,
|
|
238
|
+
"i",
|
|
239
|
+
);
|
|
211
240
|
|
|
212
241
|
/** `runtimeUnreachable` is the caller's word that the failure was the SDK's
|
|
213
242
|
* connect abort at the exec choke point (the Worker's typed check over the
|
|
@@ -234,6 +263,7 @@ export function classifyRefreshFailure(input: {
|
|
|
234
263
|
const { step, message } = input;
|
|
235
264
|
if (input.runtimeUnreachable) {
|
|
236
265
|
return {
|
|
266
|
+
step,
|
|
237
267
|
interrupted: false,
|
|
238
268
|
diskFull: false,
|
|
239
269
|
runtimeUnreachable: true,
|
|
@@ -242,6 +272,7 @@ export function classifyRefreshFailure(input: {
|
|
|
242
272
|
}
|
|
243
273
|
if (isDiskFullMessage(message)) {
|
|
244
274
|
return {
|
|
275
|
+
step,
|
|
245
276
|
interrupted: false,
|
|
246
277
|
diskFull: true,
|
|
247
278
|
runtimeUnreachable: false,
|
|
@@ -251,6 +282,7 @@ export function classifyRefreshFailure(input: {
|
|
|
251
282
|
const timedOut = /\(timed out\)/.test(message);
|
|
252
283
|
if (!timedOut && (INTERRUPTION_SIGNATURE.test(message) || RUNTIME_REPLACEMENT_WORDING.test(message))) {
|
|
253
284
|
return {
|
|
285
|
+
step,
|
|
254
286
|
interrupted: true,
|
|
255
287
|
diskFull: false,
|
|
256
288
|
runtimeUnreachable: false,
|
|
@@ -259,13 +291,20 @@ export function classifyRefreshFailure(input: {
|
|
|
259
291
|
}
|
|
260
292
|
if (input.freeKiB !== undefined && input.freeKiB !== null && input.freeKiB < DISK_FULL_FREE_KIB) {
|
|
261
293
|
return {
|
|
294
|
+
step,
|
|
262
295
|
interrupted: false,
|
|
263
296
|
diskFull: true,
|
|
264
297
|
runtimeUnreachable: false,
|
|
265
298
|
reason: diskFullReason({ step, message, freeKiB: input.freeKiB }),
|
|
266
299
|
};
|
|
267
300
|
}
|
|
268
|
-
return {
|
|
301
|
+
return {
|
|
302
|
+
step,
|
|
303
|
+
interrupted: false,
|
|
304
|
+
diskFull: false,
|
|
305
|
+
runtimeUnreachable: false,
|
|
306
|
+
reason: `${step}-failed: ${message}`,
|
|
307
|
+
};
|
|
269
308
|
}
|
|
270
309
|
|
|
271
310
|
// -- the runtime that never answers --------------------------------------------
|
|
@@ -49,6 +49,7 @@ export const RESIDENT_STEP_LABELS = {
|
|
|
49
49
|
"worktree-clean": "cleaning the worktree",
|
|
50
50
|
"clean-workspace": "cleaning the workspace",
|
|
51
51
|
"clean-before-restore": "cleaning before the restore",
|
|
52
|
+
"break-mirror": "removing the mirror's config (fault injection)",
|
|
52
53
|
"unmount-restores": "unmounting earlier restores",
|
|
53
54
|
"mirror-restore-extract": "extracting the mirror snapshot",
|
|
54
55
|
"checkout-restore-extract": "extracting the checkout snapshot",
|
|
@@ -53,6 +53,12 @@ const FLEET_BUSY_PATTERNS: readonly RegExp[] = [
|
|
|
53
53
|
// pool matches on this exact phrase. Seen live passing through as a plain
|
|
54
54
|
// in-body error and ending two reviews in under a minute each.
|
|
55
55
|
/Maximum number of running container instances exceeded/i,
|
|
56
|
+
// The platform's start-rate limit: a burst of fresh threads (twenty-four
|
|
57
|
+
// seeded sandboxes started within twenty seconds) had a third of its
|
|
58
|
+
// container starts refused with this text, and the Worker read it as a
|
|
59
|
+
// failed seed. It is capacity for a moment, like a full fleet: the
|
|
60
|
+
// executor waits and re-sends the identical request.
|
|
61
|
+
/too many containers per second/i,
|
|
56
62
|
];
|
|
57
63
|
|
|
58
64
|
export function isFleetBusy(message: string): boolean {
|
|
@@ -70,6 +70,19 @@ export interface SandboxSeed {
|
|
|
70
70
|
|
|
71
71
|
export type SeedStep = "restore" | "deps" | "fixup";
|
|
72
72
|
|
|
73
|
+
/** One restore's two phases, in milliseconds: the SDK's presigned download of
|
|
74
|
+
* the archive (judged by bytes arriving), then the extraction onto the disk. */
|
|
75
|
+
export interface RestorePhases {
|
|
76
|
+
download: number;
|
|
77
|
+
extract: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface SeedPhases {
|
|
81
|
+
checkout: RestorePhases;
|
|
82
|
+
/** Null when no deps entry rode along. */
|
|
83
|
+
deps: RestorePhases | null;
|
|
84
|
+
}
|
|
85
|
+
|
|
73
86
|
export type SeedAnswer =
|
|
74
87
|
| {
|
|
75
88
|
seeded: true;
|
|
@@ -82,6 +95,10 @@ export type SeedAnswer =
|
|
|
82
95
|
from: { ref: string; sha: string; checkoutBackupId: string; depsBackupId?: string };
|
|
83
96
|
/** Milliseconds per step; `deps` null when no entry rode along; all zero when cached. */
|
|
84
97
|
steps: { restore: number; deps: number | null; fixup: number };
|
|
98
|
+
/** Each restore split into its two phases — the archive's download to the
|
|
99
|
+
* container and its extraction onto the disk — so the gate's reading says
|
|
100
|
+
* which one owns a slow seed. Absent when cached. */
|
|
101
|
+
phases?: SeedPhases;
|
|
85
102
|
ms: number;
|
|
86
103
|
}
|
|
87
104
|
| { seeded: false; reason: SeedReason; detail: string; step?: SeedStep };
|