@coreplane/switchboard 1.238.0 → 1.240.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 +6 -0
- 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 +223 -65
- package/dist/assets/deploy/cloudflare-sandbox/Dockerfile +8 -1
- package/dist/assets/deploy/cloudflare-sandbox/worker.ts +337 -44
- package/dist/assets/deploy/cloudflare-sandbox/wrangler.template.jsonc +19 -0
- package/dist/assets/deploy/secrets.manifest.json +4 -4
- 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 +82 -0
- package/dist/assets/src/core/authz/actor.ts +33 -2
- package/dist/assets/src/core/authz/policy.ts +6 -0
- package/dist/assets/src/core/coordinator/contract.ts +12 -1
- package/dist/assets/src/core/coordinator/driver.ts +13 -0
- package/dist/assets/src/core/costs.ts +1114 -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 +70 -0
- package/dist/assets/src/core/snapshotAge.ts +30 -0
- package/dist/assets/src/core/stateWorkerRef.ts +41 -0
- 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/execution/seedPlan.ts +267 -0
- package/dist/assets/src/mcp/registry.ts +7 -0
- package/dist/assets/web/dist/.vite/manifest.json +507 -427
- package/dist/assets/web/dist/assets/AppShell-CM_fh-WW.js +1 -0
- package/dist/assets/web/dist/assets/CostsPage-BD3w5j_S.js +2 -0
- package/dist/assets/web/dist/assets/DeliveryPage-BtvzKA1m.js +1 -0
- package/dist/assets/web/dist/assets/HomePage-BU6aRCSe.js +2 -0
- package/dist/assets/web/dist/assets/NotFoundPage-DwOX1HaZ.js +1 -0
- package/dist/assets/web/dist/assets/{ResidentDetailPage-CrfCd8fx.js → ResidentDetailPage-CUAOh5d9.js} +1 -1
- package/dist/assets/web/dist/assets/ResidentsIndexPage-B3KUlYy2.js +1 -0
- package/dist/assets/web/dist/assets/RunFoldRow-Dulcbg25.js +1 -0
- package/dist/assets/web/dist/assets/RunRoutePage-DGoGrQZq.js +6 -0
- package/dist/assets/web/dist/assets/RunsIndexPage-37W7OZbC.js +1 -0
- package/dist/assets/web/dist/assets/{RunsTabs-XpCqeWu4.js → RunsTabs-BnkvCBrM.js} +1 -1
- package/dist/assets/web/dist/assets/ScheduledPage-C1y9jgAk.js +1 -0
- package/dist/assets/web/dist/assets/SettingsPage-ggfB-Rqr.js +1 -0
- package/dist/assets/web/dist/assets/SlackMark-VDNs7Vjh.js +1 -0
- package/dist/assets/web/dist/assets/Slideover-CX-LEklF.js +1 -0
- package/dist/assets/web/dist/assets/{StatusDot-DkXALvKf.js → StatusDot-CiByEtoR.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-DtLIyi5P.js → Tooltip-DyRxk8A8.js} +1 -1
- package/dist/assets/web/dist/assets/UnitRoutePage-CnWyOizV.js +1 -0
- package/dist/assets/web/dist/assets/{angular-html-DYtV9uIN.js → angular-html-DAQ_1T1v.js} +1 -1
- package/dist/assets/web/dist/assets/{angular-ts-DF2P25Zp.js → angular-ts-D4kxRVp1.js} +1 -1
- package/dist/assets/web/dist/assets/{apl-Ff96wxko.js → apl-Dy07l64y.js} +1 -1
- package/dist/assets/web/dist/assets/{astro-LbBnZna-.js → astro-DZIbTlc7.js} +1 -1
- package/dist/assets/web/dist/assets/{blade-DltuxeBW.js → blade-CmSXWX3l.js} +1 -1
- package/dist/assets/web/dist/assets/{c-DBg7yuPx.js → c-ggzVdJLK.js} +1 -1
- package/dist/assets/web/dist/assets/{chapel-C83hUAds.js → chapel-Bo1wktsG.js} +1 -1
- package/dist/assets/web/dist/assets/{cobol-DB0yV_2D.js → cobol-DJUmtKM2.js} +1 -1
- package/dist/assets/web/dist/assets/{coffee-Bpih6Kzl.js → coffee-BZWOVz4C.js} +1 -1
- package/dist/assets/web/dist/assets/{cpp-CU00ZeRn.js → cpp-Dtk39yMw.js} +1 -1
- package/dist/assets/web/dist/assets/{crystal-f8ImJkux.js → crystal-CGf_-Zoj.js} +1 -1
- package/dist/assets/web/dist/assets/{css-4VrC4ISo.js → css-CX2fcL4u.js} +1 -1
- package/dist/assets/web/dist/assets/delivery-CTdOqpVK.js +1 -0
- package/dist/assets/web/dist/assets/{dist-DtA7JiH5.js → dist-D8TaC961.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-BYS8FtgQ.js} +1 -1
- package/dist/assets/web/dist/assets/{elixir-wwIYIpJm.js → elixir-fhUpi3nO.js} +1 -1
- package/dist/assets/web/dist/assets/{elm-DyDuM3v3.js → elm-DAQNiwsN.js} +1 -1
- package/dist/assets/web/dist/assets/{erb-DFJBEVrh.js → erb-CWzEys6U.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-9rY3p-Rw.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-js-ZFnrm4J3.js → glimmer-js-tY7hy0nE.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-ts-XY4Yqshp.js → glimmer-ts-CTrLv5JF.js} +1 -1
- package/dist/assets/web/dist/assets/{glsl-xnR8efx0.js → glsl-BRJbiDfq.js} +1 -1
- package/dist/assets/web/dist/assets/{graphql-D-gO5tnP.js → graphql-B-MtVLh2.js} +1 -1
- package/dist/assets/web/dist/assets/{hack-CSxbW4ax.js → hack-BtIkOypQ.js} +1 -1
- package/dist/assets/web/dist/assets/{haml-D4ar42QT.js → haml-CoOH6gfa.js} +1 -1
- package/dist/assets/web/dist/assets/{handlebars-CMLbDE7o.js → handlebars-DgrRu-GP.js} +1 -1
- package/dist/assets/web/dist/assets/{html-CVekU4Fs.js → html-DZaaI7ca.js} +1 -1
- package/dist/assets/web/dist/assets/{html-derivative-St3HHw_N.js → html-derivative-DgHvT-qZ.js} +1 -1
- package/dist/assets/web/dist/assets/{http-Mon0DWHS.js → http-CvAd51yz.js} +1 -1
- package/dist/assets/web/dist/assets/{hurl-Cbl5IvBs.js → hurl-CXGqAfzx.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-Cse3JGb0.js} +1 -1
- package/dist/assets/web/dist/assets/{java-Bprck1l-.js → java-CIQStIXF.js} +1 -1
- package/dist/assets/web/dist/assets/{javascript-C73G1-UR.js → javascript-WbH8s7E3.js} +1 -1
- package/dist/assets/web/dist/assets/{jinja-C4DuRxYJ.js → jinja-DzyWEVKC.js} +1 -1
- package/dist/assets/web/dist/assets/{jison-B2U03GEC.js → jison-CrT-zCa-.js} +1 -1
- package/dist/assets/web/dist/assets/{json-CvTZUDzx.js → json-lHjpjXMQ.js} +1 -1
- package/dist/assets/web/dist/assets/{jsx-DMWKCzwo.js → jsx-WTwYCHW6.js} +1 -1
- package/dist/assets/web/dist/assets/{julia-Czn7bV_3.js → julia-Bqpk9H7E.js} +1 -1
- package/dist/assets/web/dist/assets/{just-BBxpNfvC.js → just-LdXk1r3m.js} +1 -1
- package/dist/assets/web/dist/assets/{latex-Bw9yg5mr.js → latex-h_y2A5KD.js} +1 -1
- package/dist/assets/web/dist/assets/{liquid-BRCjaydE.js → liquid-CY7bF2_p.js} +1 -1
- package/dist/assets/web/dist/assets/localIso-L06jV29p.js +1 -0
- package/dist/assets/web/dist/assets/{lua-DUu2KIC0.js → lua-oQ7XVZOY.js} +1 -1
- package/dist/assets/web/dist/assets/main-CGO6fQ8k.js +28 -0
- package/dist/assets/web/dist/assets/main-O8UXRuEv.css +1 -0
- package/dist/assets/web/dist/assets/{marko-CZJZIlSQ.js → marko-zv_qZ4QV.js} +1 -1
- package/dist/assets/web/dist/assets/{mdc-BmClq6fd.js → mdc-Dl4kL_YK.js} +1 -1
- package/dist/assets/web/dist/assets/{nginx-Bmt3xSEm.js → nginx-CAHCZj4F.js} +1 -1
- package/dist/assets/web/dist/assets/{nim-Da-i0utK.js → nim-BOtmwCJH.js} +1 -1
- package/dist/assets/web/dist/assets/{org-BvAre2qP.js → org-DspQk3HQ.js} +1 -1
- package/dist/assets/web/dist/assets/{perl-v9WwFFtB.js → perl-CAXmvVl0.js} +1 -1
- package/dist/assets/web/dist/assets/{php-DTU4Zyze.js → php-CMUx4VyM.js} +1 -1
- package/dist/assets/web/dist/assets/{pug-DhTwEqTO.js → pug-B55xVJTv.js} +1 -1
- package/dist/assets/web/dist/assets/{qml-BUD9AC_c.js → qml-DsNKCCRk.js} +1 -1
- package/dist/assets/web/dist/assets/{r-CAnu56n7.js → r-DbhP0VvO.js} +1 -1
- package/dist/assets/web/dist/assets/{razor-Dn0P4gAq.js → razor-BCpE6P-p.js} +1 -1
- package/dist/assets/web/dist/assets/{regexp-CZyBRaVW.js → regexp-BllsjiN4.js} +1 -1
- package/dist/assets/web/dist/assets/{rst-Ckyd5UsY.js → rst-CO5Rpq9V.js} +1 -1
- package/dist/assets/web/dist/assets/{ruby-BBQ62fsx.js → ruby-C2A4OgQq.js} +1 -1
- package/dist/assets/web/dist/assets/{sas-CW35hMxG.js → sas-Butzoyql.js} +1 -1
- package/dist/assets/web/dist/assets/{scss-CpV8pF4J.js → scss-PEUwJBpf.js} +1 -1
- package/dist/assets/web/dist/assets/settingsApi-priOPiwu.js +1 -0
- package/dist/assets/web/dist/assets/{shellscript-CRJvsmE1.js → shellscript-3Yhq0khE.js} +1 -1
- package/dist/assets/web/dist/assets/{shellsession-CyriUZxf.js → shellsession-04Hkb7m3.js} +1 -1
- package/dist/assets/web/dist/assets/{soy-D5QzYUeg.js → soy-BnId73cP.js} +1 -1
- package/dist/assets/web/dist/assets/{sql-CUBf_Mse.js → sql-BKwdc0ch.js} +1 -1
- package/dist/assets/web/dist/assets/sseReplay-DutHNghE.js +9 -0
- package/dist/assets/web/dist/assets/{stata-B-4n4ITz.js → stata-BPPtNkay.js} +1 -1
- package/dist/assets/web/dist/assets/{surrealql-RuWZBLtr.js → surrealql-CDPChcYP.js} +1 -1
- package/dist/assets/web/dist/assets/{svelte-CO5YzCsK.js → svelte-B15DIQ-z.js} +1 -1
- package/dist/assets/web/dist/assets/{templ-DbkWVaBF.js → templ-D-rp_AiB.js} +1 -1
- package/dist/assets/web/dist/assets/{tex-IuxoEcId.js → tex-BnVDCD7p.js} +1 -1
- package/dist/assets/web/dist/assets/{ts-tags-g6dGQPjd.js → ts-tags-cpujdyAR.js} +1 -1
- package/dist/assets/web/dist/assets/{tsx-WnXwiyH2.js → tsx-D1vO4L72.js} +1 -1
- package/dist/assets/web/dist/assets/{twig-Lx1G7PnY.js → twig-oQGnQGRc.js} +1 -1
- package/dist/assets/web/dist/assets/{typescript-BQd7biLF.js → typescript-CKSFpXiH.js} +1 -1
- package/dist/assets/web/dist/assets/{typst-D2lXPHK7.js → typst-DKHFo8BF.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-4wVQD9y9.js → vue-BstokpiS.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-html-DZPaRte4.js → vue-html-DpCp0_Sz.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-vine-DbmZCmu-.js → vue-vine-y5d5wUb1.js} +1 -1
- package/dist/assets/web/dist/assets/{xml-DV16szyo.js → xml-Br2hx5Ax.js} +1 -1
- package/dist/assets/web/dist/assets/{xsl-eo5-1sk0.js → xsl-Bc8Cobap.js} +1 -1
- package/dist/assets/web/dist/assets/{yaml-BXdX8HZf.js → yaml-LOITy_I1.js} +1 -1
- package/dist/cli.js +2586 -1603
- 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,
|
|
@@ -7005,16 +7132,7 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
7005
7132
|
lastRefreshError: facts?.lastRefreshError ?? null,
|
|
7006
7133
|
lastRestore: facts?.lastRestore ?? null,
|
|
7007
7134
|
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
|
-
}
|
|
7017
|
-
: null,
|
|
7135
|
+
snapshot: await this.snapshotHandle(snap),
|
|
7018
7136
|
// The provisioning schedules, the one timer a resident has (item 3).
|
|
7019
7137
|
schedules: {
|
|
7020
7138
|
provisionRun: provisionRun.length,
|
|
@@ -7051,6 +7169,9 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
7051
7169
|
// Item 64: consecutive connects the control port did not answer, with
|
|
7052
7170
|
// the rung that count is on; null while the port answers.
|
|
7053
7171
|
runtimeUnreachable: unreachable ? { ...unreachable, rung: runtimeUnreachableRung(unreachable.count) } : null,
|
|
7172
|
+
// Item 36: the auto-rebuilds this resident has had (ISO instants; the
|
|
7173
|
+
// budget's window is judged over them). Empty after a person's rebuild.
|
|
7174
|
+
autoRebuilds: (map.get(AUTO_REBUILDS_KEY) as string[] | undefined) ?? [],
|
|
7054
7175
|
};
|
|
7055
7176
|
}
|
|
7056
7177
|
|
|
@@ -7133,12 +7254,17 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
7133
7254
|
return { recreated: true, restoreStartedAt };
|
|
7134
7255
|
}
|
|
7135
7256
|
|
|
7136
|
-
/** Fault injection for
|
|
7137
|
-
* `down` with a rehydration-flavored reason
|
|
7138
|
-
*
|
|
7139
|
-
*
|
|
7140
|
-
|
|
7141
|
-
|
|
7257
|
+
/** Fault injection for item 36 without corrupting real R2 objects. Bare: persist
|
|
7258
|
+
* `down` with a rehydration-flavored reason and nothing else, so the next
|
|
7259
|
+
* watchdog pass is the backstop under test. `transition`: go through
|
|
7260
|
+
* `goDown` itself — the rebuild (or the budget stamp) happens in this call,
|
|
7261
|
+
* the way the wake path's own down does. Test-only semantics; admin scope. */
|
|
7262
|
+
async debugForceDown(reason: string, transition: boolean): Promise<ResidentStatus> {
|
|
7263
|
+
if (transition) {
|
|
7264
|
+
await this.goDown(reason);
|
|
7265
|
+
} else {
|
|
7266
|
+
await this.setResidentState("down", reason);
|
|
7267
|
+
}
|
|
7142
7268
|
return this.getStatus();
|
|
7143
7269
|
}
|
|
7144
7270
|
|
|
@@ -7154,6 +7280,8 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
7154
7280
|
defaultRef: string,
|
|
7155
7281
|
provisioningTimeoutMs: number,
|
|
7156
7282
|
dryRun: boolean,
|
|
7283
|
+
/** `auto`: item 36's rebuild, which keeps the auto-rebuild history (the budget's count); a person's rebuild clears it. */
|
|
7284
|
+
opts: { auto?: boolean } = {},
|
|
7157
7285
|
): Promise<
|
|
7158
7286
|
| {
|
|
7159
7287
|
resource: string;
|
|
@@ -7220,7 +7348,11 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
7220
7348
|
// best effort — the 1-year R2 TTL is the leak backstop
|
|
7221
7349
|
}
|
|
7222
7350
|
}
|
|
7223
|
-
|
|
7351
|
+
// A person's rebuild resets the auto-rebuild budget (item 36): the
|
|
7352
|
+
// history is what stops a flapping resident, and a person asking for a
|
|
7353
|
+
// rebuild has seen it. The automatic one keeps the history — it IS the
|
|
7354
|
+
// budget's count.
|
|
7355
|
+
if (!opts.auto) await this.ctx.storage.delete(AUTO_REBUILDS_KEY);
|
|
7224
7356
|
// From scratch means a fresh container too: the SDK's runtime identity
|
|
7225
7357
|
// forgotten and the VM destroyed (SIGKILL, a fresh disk) before
|
|
7226
7358
|
// provisioning clones onto it. A rebuild that reprovisioned onto the
|
|
@@ -7536,6 +7668,7 @@ const ROUTES: Record<string, { scope: Scope; method: string }> = {
|
|
|
7536
7668
|
"/read": { scope: "operator", method: "POST" },
|
|
7537
7669
|
"/write": { scope: "operator", method: "POST" },
|
|
7538
7670
|
"/op": { scope: "operator", method: "POST" },
|
|
7671
|
+
"/await-restore": { scope: "operator", method: "POST" },
|
|
7539
7672
|
};
|
|
7540
7673
|
|
|
7541
7674
|
/** Per-resource R2 prefix for future resident cache objects; offboard deletes
|
|
@@ -7651,6 +7784,8 @@ export default {
|
|
|
7651
7784
|
return await handleWrite(env, body);
|
|
7652
7785
|
case "/op":
|
|
7653
7786
|
return await handleOp(env, body, traceparent);
|
|
7787
|
+
case "/await-restore":
|
|
7788
|
+
return await handleAwaitRestore(env, body);
|
|
7654
7789
|
default:
|
|
7655
7790
|
return json({ error: "unknown route" }, 404);
|
|
7656
7791
|
}
|
|
@@ -8087,8 +8222,9 @@ async function handleStatus(env: Env, url: URL): Promise<Response> {
|
|
|
8087
8222
|
const resource = parseResource(url.searchParams.get("resource"));
|
|
8088
8223
|
if ("error" in resource) return json({ error: resource.error }, 400);
|
|
8089
8224
|
|
|
8090
|
-
// Body deliberately limited to
|
|
8091
|
-
// sees
|
|
8225
|
+
// Body deliberately limited to lifecycle, activity and the snapshot handle
|
|
8226
|
+
// — operator scope sees what a run needs (the seed's handle, execution.md
|
|
8227
|
+
// item 25: opaque archive ids, useless without the bucket), not config.
|
|
8092
8228
|
// Three RPCs in one flight, not one atomic snapshot: getStatus() awaits
|
|
8093
8229
|
// storage, and the DO may run other work in that gap, so `state`/`reason`
|
|
8094
8230
|
// and `inFlight` can be a hair apart (and differ slightly from a /residents
|
|
@@ -8096,21 +8232,24 @@ async function handleStatus(env: Env, url: URL): Promise<Response> {
|
|
|
8096
8232
|
// deploy gate reads /residents. The registry check rides in the same flight
|
|
8097
8233
|
// (its 404 is judged first, the probes' results discarded then).
|
|
8098
8234
|
const stub = residentStub(env, resource.resource);
|
|
8099
|
-
const [record, status, inFlight, refresh] = await Promise.all([
|
|
8235
|
+
const [record, status, inFlight, refresh, snapshot] = await Promise.all([
|
|
8100
8236
|
registryStub(env).getRecord(resource.resource),
|
|
8101
8237
|
stub.getStatus(),
|
|
8102
8238
|
stub.getInFlightCount(),
|
|
8103
8239
|
stub.getRefreshView(),
|
|
8240
|
+
stub.snapshotHandle(),
|
|
8104
8241
|
]);
|
|
8105
8242
|
if (!record) return json({ error: `${resource.resource} is not onboarded` }, 404);
|
|
8106
8243
|
// Item 7: which scheduler drives the refresh cycle and, on the Workflow
|
|
8107
|
-
// lifecycle, the current instance with its last step and the last skipped
|
|
8244
|
+
// lifecycle, the current instance with its last step and the last skipped
|
|
8245
|
+
// bucket; and the snapshot handle a seeded sandbox restores from (item 25).
|
|
8108
8246
|
return json({
|
|
8109
8247
|
state: status.state,
|
|
8110
8248
|
reason: status.reason,
|
|
8111
8249
|
inFlight,
|
|
8112
8250
|
lifecycle: refresh.lifecycle,
|
|
8113
8251
|
refresh: { instance: refresh.instance, skipped: refresh.skipped },
|
|
8252
|
+
snapshot,
|
|
8114
8253
|
});
|
|
8115
8254
|
}
|
|
8116
8255
|
|
|
@@ -8205,6 +8344,24 @@ async function handleAttach(env: Env, body: Record<string, unknown>, traceparent
|
|
|
8205
8344
|
);
|
|
8206
8345
|
}
|
|
8207
8346
|
|
|
8347
|
+
/** POST /await-restore (docs/reference/specs/execution.md item 27): the bot's
|
|
8348
|
+
* one held request while the resident restores. Held server-side by the DO's
|
|
8349
|
+
* waiter ledger; the answer streams like /attach (heartbeat whitespace then
|
|
8350
|
+
* ONE JSON document over HTTP 200) so a restore lasting minutes cannot lose
|
|
8351
|
+
* the connection. A pre-route Worker answers 404 (unknown route), which the
|
|
8352
|
+
* bot reads as "this Worker predates the route" and falls back cold. */
|
|
8353
|
+
async function handleAwaitRestore(env: Env, body: Record<string, unknown>): Promise<Response> {
|
|
8354
|
+
const resource = parseResource(body.resource);
|
|
8355
|
+
if ("error" in resource) return json({ error: resource.error }, 400);
|
|
8356
|
+
const record = await registryStub(env).getRecord(resource.resource);
|
|
8357
|
+
if (!record) return json({ error: `${resource.resource} is not onboarded` }, 404);
|
|
8358
|
+
return streamHeartbeatJson(
|
|
8359
|
+
residentStub(env, resource.resource).awaitRestore(),
|
|
8360
|
+
(result) => result,
|
|
8361
|
+
(err) => ({ error: errMsg(err), status: 500 }),
|
|
8362
|
+
);
|
|
8363
|
+
}
|
|
8364
|
+
|
|
8208
8365
|
async function handleDetach(env: Env, body: Record<string, unknown>): Promise<Response> {
|
|
8209
8366
|
const ctx = await resolveThreadRoute(env, body);
|
|
8210
8367
|
if (ctx instanceof Response) return ctx;
|
|
@@ -8487,11 +8644,12 @@ async function handleDebug(env: Env, body: Record<string, unknown>): Promise<Res
|
|
|
8487
8644
|
case "force-onboarding":
|
|
8488
8645
|
return json(await stub.debugForceOnboarding());
|
|
8489
8646
|
case "force-down": {
|
|
8490
|
-
// Fault injection for
|
|
8491
|
-
//
|
|
8647
|
+
// Fault injection for item 36; a rehydration-flavored default reason
|
|
8648
|
+
// makes it eligible. `transition: true` fires the down through goDown
|
|
8649
|
+
// (the rebuild happens now); bare, the watchdog's next pass is the backstop.
|
|
8492
8650
|
const reason =
|
|
8493
8651
|
typeof body.reason === "string" && body.reason ? body.reason : "r2-restore-failed: injected (debug force-down)";
|
|
8494
|
-
return json(await stub.debugForceDown(reason));
|
|
8652
|
+
return json(await stub.debugForceDown(reason, body.transition === true));
|
|
8495
8653
|
}
|
|
8496
8654
|
case "threads":
|
|
8497
8655
|
return json(await stub.debugThreads());
|
|
@@ -37,8 +37,15 @@ ENV COMMAND_TIMEOUT_MS=1240000
|
|
|
37
37
|
# call. The sandbox is already root with full capabilities inside its own
|
|
38
38
|
# microVM, so a container a run starts shares that run's blast radius and
|
|
39
39
|
# nothing more; no privileged flag or capability is added anywhere.
|
|
40
|
+
# squashfs-tools (`unsquashfs`): the seed (docs/reference/specs/execution.md
|
|
41
|
+
# item 25) restores the resident's snapshot with the SDK's presigned restore,
|
|
42
|
+
# which MOUNTS the archive (squashfuse + fuse-overlayfs) instead of extracting
|
|
43
|
+
# it; the Worker extracts it onto the disk the way the resident does
|
|
44
|
+
# (resident-repos item 61) so the checkout is one plain ext4 tree. The build
|
|
45
|
+
# asserts the binary with the probe the extract script uses (`command -v`).
|
|
40
46
|
RUN apt-get update \
|
|
41
|
-
&& apt-get install -y --no-install-recommends git curl ca-certificates docker.io iptables \
|
|
47
|
+
&& apt-get install -y --no-install-recommends git curl ca-certificates docker.io iptables squashfs-tools \
|
|
48
|
+
&& command -v unsquashfs >/dev/null \
|
|
42
49
|
&& curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
|
|
43
50
|
-o /usr/share/keyrings/githubcli-archive-keyring.gpg \
|
|
44
51
|
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
|