@coreplane/switchboard 1.239.0 → 1.241.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 +32 -7
- package/dist/assets/deploy/cloudflare/worker.ts +77 -18
- package/dist/assets/deploy/cloudflare-memory/worker.ts +115 -1
- package/dist/assets/deploy/cloudflare-memory/wrangler.template.jsonc +7 -1
- package/dist/assets/deploy/cloudflare-resident/worker.ts +224 -70
- package/dist/assets/deploy/secrets.manifest.json +7 -1
- package/dist/assets/package-lock.json +207 -3
- package/dist/assets/package.json +2 -1
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/agents/registry.ts +27 -8
- package/dist/assets/src/core/authz/actor.ts +38 -2
- package/dist/assets/src/core/authz/grants.ts +20 -9
- package/dist/assets/src/core/authz/policy.ts +6 -0
- package/dist/assets/src/core/coordinator/contract.ts +49 -1
- package/dist/assets/src/core/coordinator/driver.ts +30 -1
- package/dist/assets/src/core/coordinator/instancesRoute.ts +72 -0
- package/dist/assets/src/core/costs.ts +1119 -0
- package/dist/assets/src/core/costsSnapshotStore.ts +209 -0
- package/dist/assets/src/core/delivery.ts +4 -11
- package/dist/assets/src/core/deliverySnapshotStore.ts +12 -28
- package/dist/assets/src/core/ingressTokens.ts +27 -13
- package/dist/assets/src/core/runLedger/types.ts +3 -0
- package/dist/assets/src/core/runRecord.ts +10 -3
- package/dist/assets/src/core/schedules.ts +2 -1
- package/dist/assets/src/core/ship/coordinator.ts +92 -7
- package/dist/assets/src/core/snapshotAge.ts +30 -0
- package/dist/assets/src/core/stateWorkerRef.ts +41 -0
- package/dist/assets/src/core/trace/attrs.ts +1 -1
- package/dist/assets/src/execution/bindingPurge.ts +2 -2
- package/dist/assets/src/execution/residentAutoRebuild.ts +73 -0
- package/dist/assets/src/execution/residentRefresh.ts +25 -3
- package/dist/assets/src/execution/restoreWaiters.ts +61 -0
- package/dist/assets/src/mcp/registry.ts +7 -0
- package/dist/assets/web/dist/.vite/manifest.json +509 -427
- package/dist/assets/web/dist/assets/AppShell-DEy5jRuS.js +1 -0
- package/dist/assets/web/dist/assets/CostsPage-BuHD-Bv7.js +2 -0
- package/dist/assets/web/dist/assets/DeliveryPage-BGpPr5xr.js +1 -0
- package/dist/assets/web/dist/assets/HomePage-dKBvWh-E.js +2 -0
- package/dist/assets/web/dist/assets/NotFoundPage-DtmQM2gD.js +1 -0
- package/dist/assets/web/dist/assets/PendingTurnRow-CrEAWx5b.js +1 -0
- package/dist/assets/web/dist/assets/{ResidentDetailPage-CrfCd8fx.js → ResidentDetailPage-C-48fxat.js} +1 -1
- package/dist/assets/web/dist/assets/ResidentsIndexPage-CBBIjPOa.js +1 -0
- package/dist/assets/web/dist/assets/RunFoldRow-B6vxjcBI.js +1 -0
- package/dist/assets/web/dist/assets/RunRoutePage-CQjd3Cz-.js +6 -0
- package/dist/assets/web/dist/assets/RunsIndexPage-Cy9G6D0p.js +1 -0
- package/dist/assets/web/dist/assets/{RunsTabs-XpCqeWu4.js → RunsTabs-CtNGNnhN.js} +1 -1
- package/dist/assets/web/dist/assets/ScheduledPage-Ci1Ygqnc.js +1 -0
- package/dist/assets/web/dist/assets/SettingsPage-DxmJvAmT.js +1 -0
- package/dist/assets/web/dist/assets/SlackMark-VDNs7Vjh.js +1 -0
- package/dist/assets/web/dist/assets/{StatusDot-DkXALvKf.js → StatusDot-DPWE1JmO.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-DtLIyi5P.js → Tooltip-DKhSRH9t.js} +1 -1
- package/dist/assets/web/dist/assets/UnitRoutePage-DEN5bSQf.js +1 -0
- package/dist/assets/web/dist/assets/{angular-html-DYtV9uIN.js → angular-html-DgSK1qvr.js} +1 -1
- package/dist/assets/web/dist/assets/{angular-ts-DF2P25Zp.js → angular-ts-D3gNdiSG.js} +1 -1
- package/dist/assets/web/dist/assets/{apl-Ff96wxko.js → apl-CkHCYM8I.js} +1 -1
- package/dist/assets/web/dist/assets/{astro-LbBnZna-.js → astro-DLm45axt.js} +1 -1
- package/dist/assets/web/dist/assets/{blade-DltuxeBW.js → blade-BKa-VE-c.js} +1 -1
- package/dist/assets/web/dist/assets/{c-DBg7yuPx.js → c-C8NCNWai.js} +1 -1
- package/dist/assets/web/dist/assets/{chapel-C83hUAds.js → chapel-DqDQ7IKH.js} +1 -1
- package/dist/assets/web/dist/assets/{cobol-DB0yV_2D.js → cobol-j4vCD6AJ.js} +1 -1
- package/dist/assets/web/dist/assets/{coffee-Bpih6Kzl.js → coffee-C6oDVH-0.js} +1 -1
- package/dist/assets/web/dist/assets/{cpp-CU00ZeRn.js → cpp-Bd3A1Rtc.js} +1 -1
- package/dist/assets/web/dist/assets/{crystal-f8ImJkux.js → crystal-CUOgOw0d.js} +1 -1
- package/dist/assets/web/dist/assets/{css-4VrC4ISo.js → css-RYljyv7G.js} +1 -1
- package/dist/assets/web/dist/assets/delivery-CTdOqpVK.js +1 -0
- package/dist/assets/web/dist/assets/{dist-DtA7JiH5.js → dist-BnJXKHCY.js} +2 -2
- package/dist/assets/web/dist/assets/durationTone-DXG-3R7_.js +1 -0
- package/dist/assets/web/dist/assets/{edge-BwFDzfS_.js → edge-BJhNdQDc.js} +1 -1
- package/dist/assets/web/dist/assets/{elixir-wwIYIpJm.js → elixir-g1AWOAfT.js} +1 -1
- package/dist/assets/web/dist/assets/{elm-DyDuM3v3.js → elm-CER5e4Dz.js} +1 -1
- package/dist/assets/web/dist/assets/{erb-DFJBEVrh.js → erb-D5aOmMQf.js} +1 -1
- package/dist/assets/web/dist/assets/eventSource-tRprhqm7.js +1 -0
- package/dist/assets/web/dist/assets/favicon-D1hkyC5j.js +1 -0
- package/dist/assets/web/dist/assets/{git-rebase-CuDv-44X.js → git-rebase-D6rfV8jp.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-js-ZFnrm4J3.js → glimmer-js-BvDH3-mC.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-ts-XY4Yqshp.js → glimmer-ts-DChtNs9V.js} +1 -1
- package/dist/assets/web/dist/assets/{glsl-xnR8efx0.js → glsl-BHlsWrxf.js} +1 -1
- package/dist/assets/web/dist/assets/{graphql-D-gO5tnP.js → graphql-BsaBHb53.js} +1 -1
- package/dist/assets/web/dist/assets/{hack-CSxbW4ax.js → hack-0lbDosCd.js} +1 -1
- package/dist/assets/web/dist/assets/{haml-D4ar42QT.js → haml-BKtco2vr.js} +1 -1
- package/dist/assets/web/dist/assets/{handlebars-CMLbDE7o.js → handlebars-BE_cj04z.js} +1 -1
- package/dist/assets/web/dist/assets/{html-CVekU4Fs.js → html-BNcr8EVd.js} +1 -1
- package/dist/assets/web/dist/assets/{html-derivative-St3HHw_N.js → html-derivative-BsJt2Kei.js} +1 -1
- package/dist/assets/web/dist/assets/{http-Mon0DWHS.js → http-BR5P8ER_.js} +1 -1
- package/dist/assets/web/dist/assets/{hurl-Cbl5IvBs.js → hurl-CtHalpWS.js} +1 -1
- package/dist/assets/web/dist/assets/{localIso-CNA-bhS-.js → indexFormat-B-pd4r7Y.js} +1 -1
- package/dist/assets/web/dist/assets/{indexRow-FLJu5mR9.js → indexRow-BD1VT8o8.js} +1 -1
- package/dist/assets/web/dist/assets/{java-Bprck1l-.js → java-BXVdhN11.js} +1 -1
- package/dist/assets/web/dist/assets/{javascript-C73G1-UR.js → javascript-CyoShgNQ.js} +1 -1
- package/dist/assets/web/dist/assets/{jinja-C4DuRxYJ.js → jinja-BQYZzHex.js} +1 -1
- package/dist/assets/web/dist/assets/{jison-B2U03GEC.js → jison-DqrUa1Eq.js} +1 -1
- package/dist/assets/web/dist/assets/{json-CvTZUDzx.js → json-CQiTF9Jj.js} +1 -1
- package/dist/assets/web/dist/assets/{jsx-DMWKCzwo.js → jsx-Y16MkCYQ.js} +1 -1
- package/dist/assets/web/dist/assets/{julia-Czn7bV_3.js → julia-zn16YONe.js} +1 -1
- package/dist/assets/web/dist/assets/{just-BBxpNfvC.js → just-DcVeN_MN.js} +1 -1
- package/dist/assets/web/dist/assets/{latex-Bw9yg5mr.js → latex-BNNoF-WP.js} +1 -1
- package/dist/assets/web/dist/assets/{liquid-BRCjaydE.js → liquid-DAi6qHzn.js} +1 -1
- package/dist/assets/web/dist/assets/localIso-L06jV29p.js +1 -0
- package/dist/assets/web/dist/assets/{lua-DUu2KIC0.js → lua-BswS0axw.js} +1 -1
- package/dist/assets/web/dist/assets/main-B3is4sk6.js +28 -0
- package/dist/assets/web/dist/assets/main-DP_zSemY.css +1 -0
- package/dist/assets/web/dist/assets/{marko-CZJZIlSQ.js → marko-C-4hkmQZ.js} +1 -1
- package/dist/assets/web/dist/assets/{mdc-BmClq6fd.js → mdc-0ZaHUkS7.js} +1 -1
- package/dist/assets/web/dist/assets/{nginx-Bmt3xSEm.js → nginx-DBDg5tOR.js} +1 -1
- package/dist/assets/web/dist/assets/{nim-Da-i0utK.js → nim-BVMz49Yw.js} +1 -1
- package/dist/assets/web/dist/assets/{org-BvAre2qP.js → org-1hO_K6ya.js} +1 -1
- package/dist/assets/web/dist/assets/{perl-v9WwFFtB.js → perl-CVRQLvVk.js} +1 -1
- package/dist/assets/web/dist/assets/{php-DTU4Zyze.js → php-K9nCrB9n.js} +1 -1
- package/dist/assets/web/dist/assets/{pug-DhTwEqTO.js → pug-CTxOcmO3.js} +1 -1
- package/dist/assets/web/dist/assets/{qml-BUD9AC_c.js → qml-CmCkfG5q.js} +1 -1
- package/dist/assets/web/dist/assets/{r-CAnu56n7.js → r-pP47Xn1X.js} +1 -1
- package/dist/assets/web/dist/assets/{razor-Dn0P4gAq.js → razor-BpW6r3tC.js} +1 -1
- package/dist/assets/web/dist/assets/{regexp-CZyBRaVW.js → regexp-CINgdY4N.js} +1 -1
- package/dist/assets/web/dist/assets/{rst-Ckyd5UsY.js → rst-BZIUEq2Q.js} +1 -1
- package/dist/assets/web/dist/assets/{ruby-BBQ62fsx.js → ruby-nzAOxz6r.js} +1 -1
- package/dist/assets/web/dist/assets/{sas-CW35hMxG.js → sas-QIS1bFth.js} +1 -1
- package/dist/assets/web/dist/assets/{scss-CpV8pF4J.js → scss-BuhBBVNt.js} +1 -1
- package/dist/assets/web/dist/assets/settingsApi-priOPiwu.js +1 -0
- package/dist/assets/web/dist/assets/{shellscript-CRJvsmE1.js → shellscript-CQc1vXbk.js} +1 -1
- package/dist/assets/web/dist/assets/{shellsession-CyriUZxf.js → shellsession-CoubCAUv.js} +1 -1
- package/dist/assets/web/dist/assets/{soy-D5QzYUeg.js → soy-CPRzlder.js} +1 -1
- package/dist/assets/web/dist/assets/{sql-CUBf_Mse.js → sql-W9krb8-9.js} +1 -1
- package/dist/assets/web/dist/assets/sseReplay-yji8a1wM.js +9 -0
- package/dist/assets/web/dist/assets/{stata-B-4n4ITz.js → stata-CuISJEC0.js} +1 -1
- package/dist/assets/web/dist/assets/{surrealql-RuWZBLtr.js → surrealql-CSet7584.js} +1 -1
- package/dist/assets/web/dist/assets/{svelte-CO5YzCsK.js → svelte-ChXTSYwK.js} +1 -1
- package/dist/assets/web/dist/assets/{templ-DbkWVaBF.js → templ-Bm55v62k.js} +1 -1
- package/dist/assets/web/dist/assets/{tex-IuxoEcId.js → tex-CICMX8Gj.js} +1 -1
- package/dist/assets/web/dist/assets/{ts-tags-g6dGQPjd.js → ts-tags-CA1UzWyB.js} +1 -1
- package/dist/assets/web/dist/assets/{tsx-WnXwiyH2.js → tsx-Dy04HNbv.js} +1 -1
- package/dist/assets/web/dist/assets/{twig-Lx1G7PnY.js → twig-BBHsVnVD.js} +1 -1
- package/dist/assets/web/dist/assets/{typescript-BQd7biLF.js → typescript-Dvc-wVBT.js} +1 -1
- package/dist/assets/web/dist/assets/{typst-D2lXPHK7.js → typst-Dvlqx3_q.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-html-DZPaRte4.js → vue-html-D4YxT3An.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-vine-DbmZCmu-.js → vue-vine-0YE7uQlH.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-4wVQD9y9.js → vue-zWi1MNU2.js} +1 -1
- package/dist/assets/web/dist/assets/{xml-DV16szyo.js → xml-gm-iksZB.js} +1 -1
- package/dist/assets/web/dist/assets/{xsl-eo5-1sk0.js → xsl-D_W5BcrY.js} +1 -1
- package/dist/assets/web/dist/assets/{yaml-BXdX8HZf.js → yaml-CHmZ21wZ.js} +1 -1
- package/dist/cli.js +6014 -2362
- package/package.json +1 -1
- package/dist/assets/web/dist/assets/AppShell-MYHNxJks.js +0 -1
- package/dist/assets/web/dist/assets/CostsPage-DKk9OjB0.js +0 -2
- package/dist/assets/web/dist/assets/DeliveryPage-Kz6js49i.js +0 -1
- package/dist/assets/web/dist/assets/NotFoundPage-BXLrQQzy.js +0 -1
- package/dist/assets/web/dist/assets/ResidentsIndexPage-BdW0k2h3.js +0 -1
- package/dist/assets/web/dist/assets/RunFoldRow-CbTd9SiS.js +0 -9
- package/dist/assets/web/dist/assets/RunRoutePage-br0H-8Pz.js +0 -6
- package/dist/assets/web/dist/assets/RunsIndexPage-Bnzb15zH.js +0 -1
- package/dist/assets/web/dist/assets/ScheduledPage-NLhjzHAz.js +0 -1
- package/dist/assets/web/dist/assets/SettingsPage-D-i0uVw-.js +0 -1
- package/dist/assets/web/dist/assets/UnitRoutePage-DGXukEAH.js +0 -1
- package/dist/assets/web/dist/assets/durationTone-CYMux25J.js +0 -1
- package/dist/assets/web/dist/assets/favicon-CSt-qDvc.js +0 -1
- package/dist/assets/web/dist/assets/main-CFHF2RvQ.css +0 -1
- package/dist/assets/web/dist/assets/main-Dht0in4v.js +0 -28
|
@@ -156,6 +156,7 @@ import {
|
|
|
156
156
|
type WatchdogSummary,
|
|
157
157
|
} from "../../src/core/schedules.js";
|
|
158
158
|
import type { ResidentLifecycleState } from "../../src/execution/residentState.js";
|
|
159
|
+
import { RestoreWaiters } from "../../src/execution/restoreWaiters.js";
|
|
159
160
|
import {
|
|
160
161
|
decisivePull,
|
|
161
162
|
effectiveLimits,
|
|
@@ -195,6 +196,7 @@ import {
|
|
|
195
196
|
type RestoreSample,
|
|
196
197
|
type RefreshFailure,
|
|
197
198
|
} from "../../src/execution/residentRefresh.js";
|
|
199
|
+
import { autoRebuildDecision, isAutoRebuildEligible } from "../../src/execution/residentAutoRebuild.js";
|
|
198
200
|
import {
|
|
199
201
|
lifecycleOf,
|
|
200
202
|
parseLifecycle,
|
|
@@ -347,7 +349,7 @@ const BUILD_ID = buildId(BUILD);
|
|
|
347
349
|
// route is a `resident.fetch` root at the edge. Each joins the bot's trace when
|
|
348
350
|
// the request carried one. The tracer and its log sink are shared.ts's: the
|
|
349
351
|
// refresh instance's root (refresh.ts) starts from the same pair.
|
|
350
|
-
const STREAMED_ROUTES: ReadonlySet<string> = new Set(["/attach", "/exec", "/op"]);
|
|
352
|
+
const STREAMED_ROUTES: ReadonlySet<string> = new Set(["/attach", "/exec", "/op", "/await-restore"]);
|
|
351
353
|
|
|
352
354
|
/** One request as the resident's own root: started at its t0, joining the
|
|
353
355
|
* bot's trace when `traceparent` parses, the collector's steps grafted as
|
|
@@ -591,18 +593,12 @@ function isNonEvidenceReason(reason: string): boolean {
|
|
|
591
593
|
* 503 {state, reason: "mirror-busy"} instead of queueing forever. */
|
|
592
594
|
const ATTACH_MUTEX_WAIT_MS = 60_000;
|
|
593
595
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
* against the same broken build. With the 10-minute cron, N=3 ≈ 30 minutes
|
|
601
|
-
* down before the automatic escape hatch fires. `runtime-unreachable` is the
|
|
602
|
-
* ladder's last rung (item 64): a recreated container did not answer either,
|
|
603
|
-
* so the rebuild — destroy plus reprovision — is the only exit left. */
|
|
604
|
-
const AUTO_REBUILD_AFTER_STRIKES = 3;
|
|
605
|
-
const REHYDRATION_FAILURE_RE = /^(r2-restore-failed|snapshot-stamp-mismatch|no-snapshot|runtime-unreachable)/;
|
|
596
|
+
// Auto-rebuild (item 36): a resident `down` on a reason only a rebuild can
|
|
597
|
+
// escape (REHYDRATION_FAILURE_RE, `residentAutoRebuild.ts`) is rebuilt on the
|
|
598
|
+
// `goDown` transition itself, under `AUTO_REBUILD_BUDGET` per window; the
|
|
599
|
+
// watchdog is the backstop for a transition that could not act (no registry
|
|
600
|
+
// record at the time). Provision-failure downs never auto-rebuild — they would
|
|
601
|
+
// loop against the same broken build.
|
|
606
602
|
|
|
607
603
|
/** /exec budget: the shared 5-minute default (`BASH_TIMEOUT_MS`);
|
|
608
604
|
* a caller may raise it per call via the body's `timeoutMs` up to the shared
|
|
@@ -1237,6 +1233,19 @@ interface DepsBackupRecord {
|
|
|
1237
1233
|
createdAt: string;
|
|
1238
1234
|
}
|
|
1239
1235
|
|
|
1236
|
+
/** The snapshot as `/status` and `/debug info` publish it: the stamp, both
|
|
1237
|
+
* archive ids, and the deps entry archive for its lockfile key (null until
|
|
1238
|
+
* one has been taken). */
|
|
1239
|
+
interface SnapshotHandle {
|
|
1240
|
+
ref: string;
|
|
1241
|
+
sha: string;
|
|
1242
|
+
lockfileHash: string;
|
|
1243
|
+
createdAt: string;
|
|
1244
|
+
mirrorBackupId: string;
|
|
1245
|
+
checkoutBackupId: string;
|
|
1246
|
+
depsBackupId: string | null;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1240
1249
|
/** What the snapshot step answers: the record already at the stamp, a record
|
|
1241
1250
|
* it committed (with the one it replaced), or a step another writer won. */
|
|
1242
1251
|
type SnapshotStepResult =
|
|
@@ -1447,7 +1456,11 @@ const UPDATED_KEY = "resident:updatedAt";
|
|
|
1447
1456
|
const FACTS_KEY = "resident:facts";
|
|
1448
1457
|
const SNAPSHOT_KEY = "resident:snapshot";
|
|
1449
1458
|
const DEADLINE_AT_KEY = "resident:provisionDeadlineAt";
|
|
1450
|
-
|
|
1459
|
+
/** ISO instants of this resident's auto-rebuilds (item 36): the budget's window
|
|
1460
|
+
* is judged over it; a manual rebuild or an offboard clears it. */
|
|
1461
|
+
const AUTO_REBUILDS_KEY = "resident:autoRebuilds";
|
|
1462
|
+
/** The strike counter the watchdog kept before the transition rebuilt (item 36); a serving pass deletes a leftover row. */
|
|
1463
|
+
const RETIRED_REBUILD_STRIKES_KEY = "resident:rebuildStrikes";
|
|
1451
1464
|
/** Consecutive connects the container's control port did not answer (item 64): the ladder's count. */
|
|
1452
1465
|
const RUNTIME_UNREACHABLE_KEY = "resident:runtimeUnreachable";
|
|
1453
1466
|
|
|
@@ -1731,6 +1744,12 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
1731
1744
|
private incarnation = mintIncarnationId();
|
|
1732
1745
|
private leaseSeq = 0;
|
|
1733
1746
|
|
|
1747
|
+
/** Item 27: the held /await-restore requests, answered by the lifecycle
|
|
1748
|
+
* transition out of `restoring` (`setResidentState`). In-memory on purpose:
|
|
1749
|
+
* a DO restart drops the held RPCs with the ledger and the bot's own
|
|
1750
|
+
* deadline names the fallback. */
|
|
1751
|
+
private restoreWaiters = new RestoreWaiters();
|
|
1752
|
+
|
|
1734
1753
|
private nextHolder(): string {
|
|
1735
1754
|
return `${this.incarnation}:${++this.leaseSeq}`;
|
|
1736
1755
|
}
|
|
@@ -2527,8 +2546,19 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
2527
2546
|
} catch (err) {
|
|
2528
2547
|
// The typed and cause-chain check first: the SDK's replacement errors
|
|
2529
2548
|
// (a stale process handle, an inactive runtime identity, an interrupted
|
|
2530
|
-
// operation) carry the wording one cause down or not at all.
|
|
2531
|
-
|
|
2549
|
+
// operation) carry the wording one cause down or not at all. An extract
|
|
2550
|
+
// the container roll SIGTERMed is different: its exec comes back a
|
|
2551
|
+
// normal `exit 143` result and the SDK's wording only reaches the execs
|
|
2552
|
+
// that follow — so the disposition also reads the kill signature, and
|
|
2553
|
+
// asks the runtime whether the container the extract wrote to is still
|
|
2554
|
+
// there (the incident that named this: a resident went `down` on exactly
|
|
2555
|
+
// this, 10 ms before every exec answered "container is not running").
|
|
2556
|
+
const runtimeReplaced = isRuntimeReplacement(err);
|
|
2557
|
+
const runtimeActive = runtimeReplaced ? false : await this.isRuntimeActive().catch(() => null);
|
|
2558
|
+
const disposition = restoreFailureDisposition(errMsg(err), {
|
|
2559
|
+
runtimeReplaced,
|
|
2560
|
+
runtimeActive: runtimeActive ?? undefined,
|
|
2561
|
+
});
|
|
2532
2562
|
if (disposition.action === "interrupted") {
|
|
2533
2563
|
// The runtime was replaced under the restore (a resident Worker deploy
|
|
2534
2564
|
// rolled the container): the disk the stream wrote to is gone with the
|
|
@@ -2544,7 +2574,10 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
2544
2574
|
console.log(`restore: interrupted by a runtime replacement — ${disposition.reason.slice(0, 400)}`);
|
|
2545
2575
|
await this.recordRefreshError(disposition.reason);
|
|
2546
2576
|
await this.setResidentState("degraded", disposition.reason);
|
|
2547
|
-
|
|
2577
|
+
// The reason travels on the error: the instance step's classifier
|
|
2578
|
+
// reads `restore-interrupted:` as an interruption whether the verdict
|
|
2579
|
+
// came from the SDK's wording, the kill signature or the probe.
|
|
2580
|
+
throw new StepError(err instanceof StepError ? err.step : "restore", disposition.reason);
|
|
2548
2581
|
}
|
|
2549
2582
|
// A stalled or capped restore is STILL STREAMING (the SDK call cannot be
|
|
2550
2583
|
// cancelled); `pendingRestores` keeps the next hydrate off its directory,
|
|
@@ -2600,9 +2633,62 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
2600
2633
|
* instance: the cron's decision reads the state). */
|
|
2601
2634
|
private async goDown(reason: string): Promise<ResidentDownError> {
|
|
2602
2635
|
await this.setResidentState("down", reason);
|
|
2636
|
+
// Item 36: the transition is the trigger — a rehydration down is rebuilt
|
|
2637
|
+
// now (the rebuild arms provisioning's own schedule and returns), not
|
|
2638
|
+
// after three watchdog passes. The error still says `down`: the caller's
|
|
2639
|
+
// step ends the way it always did, and the rebuild's provisioning owns the
|
|
2640
|
+
// disk from its callback on. A failure inside the judgement is logged, not
|
|
2641
|
+
// thrown: the caller's contract is the ResidentDownError, and the state is
|
|
2642
|
+
// already `down`, where the watchdog's backstop pass finds it.
|
|
2643
|
+
await this.autoRebuildFromDown(reason, "transition").catch((err) =>
|
|
2644
|
+
console.log(`auto-rebuild (transition): failed — ${errMsg(err)}; the watchdog's pass is the backstop`),
|
|
2645
|
+
);
|
|
2603
2646
|
return new ResidentDownError(reason);
|
|
2604
2647
|
}
|
|
2605
2648
|
|
|
2649
|
+
/** Item 36: judge a `down` for the automatic rebuild and act. `rebuilt` when
|
|
2650
|
+
* the rebuild started (state is `onboarding` now); `budget-spent` when the
|
|
2651
|
+
* resident stays down with the budget stamped on its reason; `none` for a
|
|
2652
|
+
* reason a rebuild cannot help, one already stamped, or a registry record
|
|
2653
|
+
* missing (an offboard mid-flight — the watchdog's pass is the backstop).
|
|
2654
|
+
* Both callers — the transition and the watchdog — read and write the one
|
|
2655
|
+
* history row, so they cannot disagree about the budget. */
|
|
2656
|
+
private async autoRebuildFromDown(
|
|
2657
|
+
reason: string,
|
|
2658
|
+
where: "transition" | "watchdog",
|
|
2659
|
+
): Promise<"rebuilt" | "budget-spent" | "none"> {
|
|
2660
|
+
if (!isAutoRebuildEligible(reason)) return "none";
|
|
2661
|
+
const history = (await this.ctx.storage.get<string[]>(AUTO_REBUILDS_KEY)) ?? [];
|
|
2662
|
+
const decision = autoRebuildDecision({ reason, history, now: systemClock() });
|
|
2663
|
+
if (decision.action === "not-eligible") return "none";
|
|
2664
|
+
if (decision.action === "budget-spent") {
|
|
2665
|
+
console.log(`auto-rebuild (${where}): ${decision.reason.slice(0, 400)}`);
|
|
2666
|
+
await this.ctx.storage.put(AUTO_REBUILDS_KEY, decision.history);
|
|
2667
|
+
await this.setResidentState("down", decision.reason);
|
|
2668
|
+
return "budget-spent";
|
|
2669
|
+
}
|
|
2670
|
+
const resource = (await this.ctx.storage.get<string>(RESOURCE_KEY)) ?? "";
|
|
2671
|
+
const record = await this.registry()
|
|
2672
|
+
.getRecord(resource)
|
|
2673
|
+
.catch(() => null);
|
|
2674
|
+
if (!record) {
|
|
2675
|
+
console.log(`auto-rebuild (${where}): no registry record for the resident — left down (${reason.slice(0, 200)})`);
|
|
2676
|
+
return "none";
|
|
2677
|
+
}
|
|
2678
|
+
console.log(`auto-rebuild (${where}): ${decision.reason.slice(0, 400)}`);
|
|
2679
|
+
const result = await this.rebuild(resource, record.defaultRef, record.provisioningTimeoutMs, false, {
|
|
2680
|
+
auto: true,
|
|
2681
|
+
});
|
|
2682
|
+
if ("error" in result) {
|
|
2683
|
+
console.log(`auto-rebuild (${where}): rebuild refused — ${result.error}`);
|
|
2684
|
+
return "none";
|
|
2685
|
+
}
|
|
2686
|
+
// The instant is recorded once the rebuild has started: a refused rebuild
|
|
2687
|
+
// spends no budget.
|
|
2688
|
+
await this.ctx.storage.put(AUTO_REBUILDS_KEY, decision.history);
|
|
2689
|
+
return "rebuilt";
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2606
2692
|
private async recordRefreshError(message: string): Promise<void> {
|
|
2607
2693
|
const facts = await this.ctx.storage.get<RepoFacts>(FACTS_KEY);
|
|
2608
2694
|
if (facts) await this.ctx.storage.put(FACTS_KEY, { ...facts, lastRefreshError: residentText(message) });
|
|
@@ -3383,8 +3469,8 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
3383
3469
|
await this.recreateContainer(reason);
|
|
3384
3470
|
throw err;
|
|
3385
3471
|
case "down":
|
|
3386
|
-
// A fresh VM did not answer either. Down with a
|
|
3387
|
-
// (REHYDRATION_FAILURE_RE) —
|
|
3472
|
+
// A fresh VM did not answer either. Down with a rebuild-eligible reason
|
|
3473
|
+
// (REHYDRATION_FAILURE_RE) — goDown rebuilds on the transition, item 36 —
|
|
3388
3474
|
// and the VM destroyed, so the rebuild's provisioning starts on a new one.
|
|
3389
3475
|
this.swapIncarnation(); // deliberate incarnation swap
|
|
3390
3476
|
await this.destroy().catch((destroyErr) =>
|
|
@@ -4281,8 +4367,8 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
4281
4367
|
|
|
4282
4368
|
/** One watchdog pass over this resident (invoked by the Worker cron): time
|
|
4283
4369
|
* out an onboarding stuck past its budget → down(provision-timeout) + cap
|
|
4284
|
-
* slot release;
|
|
4285
|
-
*
|
|
4370
|
+
* slot release; the backstop for item 36's auto-rebuild (a rehydration
|
|
4371
|
+
* down the transition could not act on — same decision, same row); name a
|
|
4286
4372
|
* `refreshing`/`restoring` marker older than STALE_MIDFLIGHT_MS that no
|
|
4287
4373
|
* live lease and no running instance stands behind —
|
|
4288
4374
|
* `degraded(stale-mid-flight: …)` carrying the last instance's id and the
|
|
@@ -4324,28 +4410,26 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
4324
4410
|
return { resource, ...status, action: "none" };
|
|
4325
4411
|
}
|
|
4326
4412
|
if (status.state === "down") {
|
|
4327
|
-
//
|
|
4328
|
-
//
|
|
4329
|
-
//
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
return { resource, state: "onboarding", reason, action: "auto-rebuilt" };
|
|
4340
|
-
}
|
|
4341
|
-
}
|
|
4342
|
-
await this.ctx.storage.put(REBUILD_STRIKES_KEY, strikes);
|
|
4413
|
+
// Item 36's backstop: the transition rebuilt (or stamped the budget)
|
|
4414
|
+
// when it fired; a rehydration down still here on a pass is one the
|
|
4415
|
+
// transition could not act on — a registry record missing then, or a
|
|
4416
|
+
// down from before the transition rebuilt. Same decision, same history
|
|
4417
|
+
// row, no strikes to accumulate.
|
|
4418
|
+
if ((await this.autoRebuildFromDown(status.reason, "watchdog")) === "rebuilt") {
|
|
4419
|
+
return {
|
|
4420
|
+
resource,
|
|
4421
|
+
state: "onboarding",
|
|
4422
|
+
reason: `auto-rebuild (watchdog): ${status.reason}`,
|
|
4423
|
+
action: "auto-rebuilt",
|
|
4424
|
+
};
|
|
4343
4425
|
}
|
|
4344
|
-
return { resource, ...
|
|
4426
|
+
return { resource, ...(await this.getStatus()), action: "none" };
|
|
4345
4427
|
}
|
|
4346
|
-
//
|
|
4347
|
-
//
|
|
4348
|
-
|
|
4428
|
+
// The auto-rebuild history is NOT cleared by a serving state: the budget
|
|
4429
|
+
// counts rebuilds inside a window, so a resident that comes back `warm`
|
|
4430
|
+
// and goes `down` again is judged with its earlier rebuilds in view. The
|
|
4431
|
+
// strike counter the watchdog kept before is a retired row.
|
|
4432
|
+
await this.ctx.storage.delete(RETIRED_REBUILD_STRIKES_KEY);
|
|
4349
4433
|
|
|
4350
4434
|
// A mid-flight state older than STALE_MIDFLIGHT_MS with no cycle or restore
|
|
4351
4435
|
// actually running is a marker orphaned by a cycle that died — a DO evicted
|
|
@@ -6911,6 +6995,29 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
6911
6995
|
[REASON_KEY]: residentText(reason),
|
|
6912
6996
|
[UPDATED_KEY]: new Date(systemClock()).toISOString(),
|
|
6913
6997
|
});
|
|
6998
|
+
// Item 27: the transition OUT of `restoring` is the restore event — it
|
|
6999
|
+
// answers every held /await-restore request with the state landed on.
|
|
7000
|
+
if (state !== "restoring") this.restoreWaiters.publish({ state, reason: residentText(reason) });
|
|
7001
|
+
}
|
|
7002
|
+
|
|
7003
|
+
/** POST /await-restore (docs/reference/specs/execution.md item 27): answer at
|
|
7004
|
+
* once when the state is anything but `restoring`; otherwise hold the ONE
|
|
7005
|
+
* request on the waiter ledger until `setResidentState` leaves `restoring`
|
|
7006
|
+
* and publishes. No polling and no retry timer on either side. */
|
|
7007
|
+
async awaitRestore(): Promise<{ state: string; reason: string }> {
|
|
7008
|
+
// The waiter goes on the ledger BEFORE the state is read: the read awaits
|
|
7009
|
+
// storage, and a transition that lands in that gap would publish to a
|
|
7010
|
+
// ledger this request is not on yet and hold it for a transition that may
|
|
7011
|
+
// never come (the bot's deadline would then turn a short wait cold). A
|
|
7012
|
+
// state already out of `restoring` answers at once and withdraws the waiter.
|
|
7013
|
+
const held = this.restoreWaiters.hold();
|
|
7014
|
+
const status = await this.getStatus();
|
|
7015
|
+
if (status.state !== "restoring") {
|
|
7016
|
+
held.withdraw();
|
|
7017
|
+
return { state: status.state, reason: status.reason };
|
|
7018
|
+
}
|
|
7019
|
+
const outcome = await held.outcome;
|
|
7020
|
+
return { state: outcome.state, reason: outcome.reason };
|
|
6914
7021
|
}
|
|
6915
7022
|
|
|
6916
7023
|
async getStatus(): Promise<ResidentStatus> {
|
|
@@ -6925,6 +7032,26 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
6925
7032
|
/** Full admin-facing view (surfaced via GET /residents and /debug info):
|
|
6926
7033
|
* lifecycle + recorded sha/cache keys/snapshot stamp/refresh telemetry.
|
|
6927
7034
|
* Backup handles are reduced to ids — never the raw handle internals. */
|
|
7035
|
+
/** The stamped snapshot as a handle (docs/reference/specs/resident-repos.md
|
|
7036
|
+
* item 7): the stamp, both archive ids, and the deps-store entry archive for
|
|
7037
|
+
* the snapshot's own lockfile key when one has been taken (item 61). What
|
|
7038
|
+
* `/status` publishes for the seed (execution.md item 25 — a seeded sandbox
|
|
7039
|
+
* restores the checkout and the deps view from it) and what `/debug info`
|
|
7040
|
+
* shows; null before the first snapshot. */
|
|
7041
|
+
async snapshotHandle(snap?: SnapshotRecord): Promise<SnapshotHandle | null> {
|
|
7042
|
+
const record = snap ?? (await this.ctx.storage.get<SnapshotRecord>(SNAPSHOT_KEY));
|
|
7043
|
+
if (!record) return null;
|
|
7044
|
+
return {
|
|
7045
|
+
ref: record.ref,
|
|
7046
|
+
sha: record.sha,
|
|
7047
|
+
lockfileHash: record.lockfileHash,
|
|
7048
|
+
createdAt: record.createdAt,
|
|
7049
|
+
mirrorBackupId: record.mirror.id,
|
|
7050
|
+
checkoutBackupId: record.checkout.id,
|
|
7051
|
+
depsBackupId: (await this.depsBackupRecord(record.lockfileHash))?.backup.id ?? null,
|
|
7052
|
+
};
|
|
7053
|
+
}
|
|
7054
|
+
|
|
6928
7055
|
async getResidentInfo(): Promise<Record<string, unknown>> {
|
|
6929
7056
|
const map = await this.ctx.storage.get<unknown>([
|
|
6930
7057
|
RESOURCE_KEY,
|
|
@@ -6940,6 +7067,7 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
6940
7067
|
LIFECYCLE_KEY,
|
|
6941
7068
|
REFRESH_INSTANCE_KEY,
|
|
6942
7069
|
RUNTIME_UNREACHABLE_KEY,
|
|
7070
|
+
AUTO_REBUILDS_KEY,
|
|
6943
7071
|
]);
|
|
6944
7072
|
const facts = map.get(FACTS_KEY) as RepoFacts | undefined;
|
|
6945
7073
|
const snap = map.get(SNAPSHOT_KEY) as SnapshotRecord | undefined;
|
|
@@ -7005,21 +7133,7 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
7005
7133
|
lastRefreshError: facts?.lastRefreshError ?? null,
|
|
7006
7134
|
lastRestore: facts?.lastRestore ?? null,
|
|
7007
7135
|
idleSince: facts?.idleSince ?? null,
|
|
7008
|
-
snapshot: snap
|
|
7009
|
-
? {
|
|
7010
|
-
ref: snap.ref,
|
|
7011
|
-
sha: snap.sha,
|
|
7012
|
-
lockfileHash: snap.lockfileHash,
|
|
7013
|
-
createdAt: snap.createdAt,
|
|
7014
|
-
mirrorBackupId: snap.mirror.id,
|
|
7015
|
-
checkoutBackupId: snap.checkout.id,
|
|
7016
|
-
// The seed handle's other half (docs/reference/specs/execution.md item 25):
|
|
7017
|
-
// the deps-store entry archive for the snapshot's own lockfile key,
|
|
7018
|
-
// when one has been taken (item 61) — a seeded sandbox restores it
|
|
7019
|
-
// beside the checkout and skips the install.
|
|
7020
|
-
depsBackupId: (await this.depsBackupRecord(snap.lockfileHash))?.backup.id ?? null,
|
|
7021
|
-
}
|
|
7022
|
-
: null,
|
|
7136
|
+
snapshot: await this.snapshotHandle(snap),
|
|
7023
7137
|
// The provisioning schedules, the one timer a resident has (item 3).
|
|
7024
7138
|
schedules: {
|
|
7025
7139
|
provisionRun: provisionRun.length,
|
|
@@ -7056,6 +7170,9 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
7056
7170
|
// Item 64: consecutive connects the control port did not answer, with
|
|
7057
7171
|
// the rung that count is on; null while the port answers.
|
|
7058
7172
|
runtimeUnreachable: unreachable ? { ...unreachable, rung: runtimeUnreachableRung(unreachable.count) } : null,
|
|
7173
|
+
// Item 36: the auto-rebuilds this resident has had (ISO instants; the
|
|
7174
|
+
// budget's window is judged over them). Empty after a person's rebuild.
|
|
7175
|
+
autoRebuilds: (map.get(AUTO_REBUILDS_KEY) as string[] | undefined) ?? [],
|
|
7059
7176
|
};
|
|
7060
7177
|
}
|
|
7061
7178
|
|
|
@@ -7138,12 +7255,17 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
7138
7255
|
return { recreated: true, restoreStartedAt };
|
|
7139
7256
|
}
|
|
7140
7257
|
|
|
7141
|
-
/** Fault injection for
|
|
7142
|
-
* `down` with a rehydration-flavored reason
|
|
7143
|
-
*
|
|
7144
|
-
*
|
|
7145
|
-
|
|
7146
|
-
|
|
7258
|
+
/** Fault injection for item 36 without corrupting real R2 objects. Bare: persist
|
|
7259
|
+
* `down` with a rehydration-flavored reason and nothing else, so the next
|
|
7260
|
+
* watchdog pass is the backstop under test. `transition`: go through
|
|
7261
|
+
* `goDown` itself — the rebuild (or the budget stamp) happens in this call,
|
|
7262
|
+
* the way the wake path's own down does. Test-only semantics; admin scope. */
|
|
7263
|
+
async debugForceDown(reason: string, transition: boolean): Promise<ResidentStatus> {
|
|
7264
|
+
if (transition) {
|
|
7265
|
+
await this.goDown(reason);
|
|
7266
|
+
} else {
|
|
7267
|
+
await this.setResidentState("down", reason);
|
|
7268
|
+
}
|
|
7147
7269
|
return this.getStatus();
|
|
7148
7270
|
}
|
|
7149
7271
|
|
|
@@ -7159,6 +7281,8 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
7159
7281
|
defaultRef: string,
|
|
7160
7282
|
provisioningTimeoutMs: number,
|
|
7161
7283
|
dryRun: boolean,
|
|
7284
|
+
/** `auto`: item 36's rebuild, which keeps the auto-rebuild history (the budget's count); a person's rebuild clears it. */
|
|
7285
|
+
opts: { auto?: boolean } = {},
|
|
7162
7286
|
): Promise<
|
|
7163
7287
|
| {
|
|
7164
7288
|
resource: string;
|
|
@@ -7225,7 +7349,11 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
7225
7349
|
// best effort — the 1-year R2 TTL is the leak backstop
|
|
7226
7350
|
}
|
|
7227
7351
|
}
|
|
7228
|
-
|
|
7352
|
+
// A person's rebuild resets the auto-rebuild budget (item 36): the
|
|
7353
|
+
// history is what stops a flapping resident, and a person asking for a
|
|
7354
|
+
// rebuild has seen it. The automatic one keeps the history — it IS the
|
|
7355
|
+
// budget's count.
|
|
7356
|
+
if (!opts.auto) await this.ctx.storage.delete(AUTO_REBUILDS_KEY);
|
|
7229
7357
|
// From scratch means a fresh container too: the SDK's runtime identity
|
|
7230
7358
|
// forgotten and the VM destroyed (SIGKILL, a fresh disk) before
|
|
7231
7359
|
// provisioning clones onto it. A rebuild that reprovisioned onto the
|
|
@@ -7541,6 +7669,7 @@ const ROUTES: Record<string, { scope: Scope; method: string }> = {
|
|
|
7541
7669
|
"/read": { scope: "operator", method: "POST" },
|
|
7542
7670
|
"/write": { scope: "operator", method: "POST" },
|
|
7543
7671
|
"/op": { scope: "operator", method: "POST" },
|
|
7672
|
+
"/await-restore": { scope: "operator", method: "POST" },
|
|
7544
7673
|
};
|
|
7545
7674
|
|
|
7546
7675
|
/** Per-resource R2 prefix for future resident cache objects; offboard deletes
|
|
@@ -7656,6 +7785,8 @@ export default {
|
|
|
7656
7785
|
return await handleWrite(env, body);
|
|
7657
7786
|
case "/op":
|
|
7658
7787
|
return await handleOp(env, body, traceparent);
|
|
7788
|
+
case "/await-restore":
|
|
7789
|
+
return await handleAwaitRestore(env, body);
|
|
7659
7790
|
default:
|
|
7660
7791
|
return json({ error: "unknown route" }, 404);
|
|
7661
7792
|
}
|
|
@@ -8092,8 +8223,9 @@ async function handleStatus(env: Env, url: URL): Promise<Response> {
|
|
|
8092
8223
|
const resource = parseResource(url.searchParams.get("resource"));
|
|
8093
8224
|
if ("error" in resource) return json({ error: resource.error }, 400);
|
|
8094
8225
|
|
|
8095
|
-
// Body deliberately limited to
|
|
8096
|
-
// sees
|
|
8226
|
+
// Body deliberately limited to lifecycle, activity and the snapshot handle
|
|
8227
|
+
// — operator scope sees what a run needs (the seed's handle, execution.md
|
|
8228
|
+
// item 25: opaque archive ids, useless without the bucket), not config.
|
|
8097
8229
|
// Three RPCs in one flight, not one atomic snapshot: getStatus() awaits
|
|
8098
8230
|
// storage, and the DO may run other work in that gap, so `state`/`reason`
|
|
8099
8231
|
// and `inFlight` can be a hair apart (and differ slightly from a /residents
|
|
@@ -8101,21 +8233,24 @@ async function handleStatus(env: Env, url: URL): Promise<Response> {
|
|
|
8101
8233
|
// deploy gate reads /residents. The registry check rides in the same flight
|
|
8102
8234
|
// (its 404 is judged first, the probes' results discarded then).
|
|
8103
8235
|
const stub = residentStub(env, resource.resource);
|
|
8104
|
-
const [record, status, inFlight, refresh] = await Promise.all([
|
|
8236
|
+
const [record, status, inFlight, refresh, snapshot] = await Promise.all([
|
|
8105
8237
|
registryStub(env).getRecord(resource.resource),
|
|
8106
8238
|
stub.getStatus(),
|
|
8107
8239
|
stub.getInFlightCount(),
|
|
8108
8240
|
stub.getRefreshView(),
|
|
8241
|
+
stub.snapshotHandle(),
|
|
8109
8242
|
]);
|
|
8110
8243
|
if (!record) return json({ error: `${resource.resource} is not onboarded` }, 404);
|
|
8111
8244
|
// Item 7: which scheduler drives the refresh cycle and, on the Workflow
|
|
8112
|
-
// lifecycle, the current instance with its last step and the last skipped
|
|
8245
|
+
// lifecycle, the current instance with its last step and the last skipped
|
|
8246
|
+
// bucket; and the snapshot handle a seeded sandbox restores from (item 25).
|
|
8113
8247
|
return json({
|
|
8114
8248
|
state: status.state,
|
|
8115
8249
|
reason: status.reason,
|
|
8116
8250
|
inFlight,
|
|
8117
8251
|
lifecycle: refresh.lifecycle,
|
|
8118
8252
|
refresh: { instance: refresh.instance, skipped: refresh.skipped },
|
|
8253
|
+
snapshot,
|
|
8119
8254
|
});
|
|
8120
8255
|
}
|
|
8121
8256
|
|
|
@@ -8210,6 +8345,24 @@ async function handleAttach(env: Env, body: Record<string, unknown>, traceparent
|
|
|
8210
8345
|
);
|
|
8211
8346
|
}
|
|
8212
8347
|
|
|
8348
|
+
/** POST /await-restore (docs/reference/specs/execution.md item 27): the bot's
|
|
8349
|
+
* one held request while the resident restores. Held server-side by the DO's
|
|
8350
|
+
* waiter ledger; the answer streams like /attach (heartbeat whitespace then
|
|
8351
|
+
* ONE JSON document over HTTP 200) so a restore lasting minutes cannot lose
|
|
8352
|
+
* the connection. A pre-route Worker answers 404 (unknown route), which the
|
|
8353
|
+
* bot reads as "this Worker predates the route" and falls back cold. */
|
|
8354
|
+
async function handleAwaitRestore(env: Env, body: Record<string, unknown>): Promise<Response> {
|
|
8355
|
+
const resource = parseResource(body.resource);
|
|
8356
|
+
if ("error" in resource) return json({ error: resource.error }, 400);
|
|
8357
|
+
const record = await registryStub(env).getRecord(resource.resource);
|
|
8358
|
+
if (!record) return json({ error: `${resource.resource} is not onboarded` }, 404);
|
|
8359
|
+
return streamHeartbeatJson(
|
|
8360
|
+
residentStub(env, resource.resource).awaitRestore(),
|
|
8361
|
+
(result) => result,
|
|
8362
|
+
(err) => ({ error: errMsg(err), status: 500 }),
|
|
8363
|
+
);
|
|
8364
|
+
}
|
|
8365
|
+
|
|
8213
8366
|
async function handleDetach(env: Env, body: Record<string, unknown>): Promise<Response> {
|
|
8214
8367
|
const ctx = await resolveThreadRoute(env, body);
|
|
8215
8368
|
if (ctx instanceof Response) return ctx;
|
|
@@ -8492,11 +8645,12 @@ async function handleDebug(env: Env, body: Record<string, unknown>): Promise<Res
|
|
|
8492
8645
|
case "force-onboarding":
|
|
8493
8646
|
return json(await stub.debugForceOnboarding());
|
|
8494
8647
|
case "force-down": {
|
|
8495
|
-
// Fault injection for
|
|
8496
|
-
//
|
|
8648
|
+
// Fault injection for item 36; a rehydration-flavored default reason
|
|
8649
|
+
// makes it eligible. `transition: true` fires the down through goDown
|
|
8650
|
+
// (the rebuild happens now); bare, the watchdog's next pass is the backstop.
|
|
8497
8651
|
const reason =
|
|
8498
8652
|
typeof body.reason === "string" && body.reason ? body.reason : "r2-restore-failed: injected (debug force-down)";
|
|
8499
|
-
return json(await stub.debugForceDown(reason));
|
|
8653
|
+
return json(await stub.debugForceDown(reason, body.transition === true));
|
|
8500
8654
|
}
|
|
8501
8655
|
case "threads":
|
|
8502
8656
|
return json(await stub.debugThreads());
|
|
@@ -38,7 +38,13 @@
|
|
|
38
38
|
"name": "SWITCHBOARD_INGRESS_TOKENS",
|
|
39
39
|
"workers": ["bot"],
|
|
40
40
|
"optional": true,
|
|
41
|
-
"note": "JSON bearer→identity map for POST /ingress and POST /mcp. Self-minted (openssl rand -hex 32 per entry). Optional: without it /ingress and /mcp are disabled and `deploy restart` has no bearer. The `cron` entry fires schedules; a `coordinator` entry (granted `coordinator:step`) is the ship coordinator's, rotated apart from cron."
|
|
41
|
+
"note": "JSON bearer→identity map for POST /ingress and POST /mcp: `{ subject, channel?, email? }` per entry. Self-minted (openssl rand -hex 32 per entry). Optional: without it /ingress and /mcp are disabled and `deploy restart` has no bearer. The `cron` entry fires schedules; a `coordinator` entry (granted `coordinator:step`) is the ship coordinator's, rotated apart from cron. An `email` binds the token to a person: their runs, their grants untouched (authorization.md item 15)."
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"name": "GITHUB_WEBHOOK_SECRET",
|
|
45
|
+
"workers": ["bot"],
|
|
46
|
+
"optional": true,
|
|
47
|
+
"note": "GitHub webhook signing secret for POST /webhooks/github (the check-run intake). Set the same value on the repo's webhook. Optional: without it the intake answers 503 disabled and the merge wait falls back to its bounded timeout."
|
|
42
48
|
},
|
|
43
49
|
{
|
|
44
50
|
"name": "SANDBOX_TOKEN",
|