@coreplane/switchboard 0.0.0 → 1.18.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.
Files changed (131) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +18 -1
  3. package/dist/assets/.dockerignore +27 -0
  4. package/dist/assets/.env.example +33 -0
  5. package/dist/assets/Dockerfile +111 -0
  6. package/dist/assets/config/config.example.yaml +359 -0
  7. package/dist/assets/deploy/bin/build-stamp.d.mts +15 -0
  8. package/dist/assets/deploy/bin/build-stamp.mjs +98 -0
  9. package/dist/assets/deploy/bin/cf-logs +32 -0
  10. package/dist/assets/deploy/cloudflare/package.json +29 -0
  11. package/dist/assets/deploy/cloudflare/preflight.mjs +243 -0
  12. package/dist/assets/deploy/cloudflare/tsconfig.json +18 -0
  13. package/dist/assets/deploy/cloudflare/worker.ts +382 -0
  14. package/dist/assets/deploy/cloudflare/wrangler.template.jsonc +67 -0
  15. package/dist/assets/deploy/cloudflare/write-build.d.mts +7 -0
  16. package/dist/assets/deploy/cloudflare/write-build.mjs +53 -0
  17. package/dist/assets/deploy/cloudflare-docs/package.json +18 -0
  18. package/dist/assets/deploy/cloudflare-docs/wrangler.template.jsonc +30 -0
  19. package/dist/assets/deploy/cloudflare-memory/package.json +25 -0
  20. package/dist/assets/deploy/cloudflare-memory/tsconfig.json +17 -0
  21. package/dist/assets/deploy/cloudflare-memory/worker.ts +2635 -0
  22. package/dist/assets/deploy/cloudflare-memory/wrangler.template.jsonc +50 -0
  23. package/dist/assets/deploy/cloudflare-resident/Dockerfile +91 -0
  24. package/dist/assets/deploy/cloudflare-resident/gc.ts +287 -0
  25. package/dist/assets/deploy/cloudflare-resident/node-async-hooks.d.ts +11 -0
  26. package/dist/assets/deploy/cloudflare-resident/package.json +29 -0
  27. package/dist/assets/deploy/cloudflare-resident/preflight.mjs +224 -0
  28. package/dist/assets/deploy/cloudflare-resident/tsconfig.json +19 -0
  29. package/dist/assets/deploy/cloudflare-resident/worker.ts +6637 -0
  30. package/dist/assets/deploy/cloudflare-resident/wrangler.template.jsonc +120 -0
  31. package/dist/assets/deploy/cloudflare-sandbox/Dockerfile +67 -0
  32. package/dist/assets/deploy/cloudflare-sandbox/docker-wrapper.sh +37 -0
  33. package/dist/assets/deploy/cloudflare-sandbox/package.json +26 -0
  34. package/dist/assets/deploy/cloudflare-sandbox/tsconfig.json +20 -0
  35. package/dist/assets/deploy/cloudflare-sandbox/worker.ts +410 -0
  36. package/dist/assets/deploy/cloudflare-sandbox/wrangler.template.jsonc +67 -0
  37. package/dist/assets/deploy/profile.example.json +13 -0
  38. package/dist/assets/deploy/secrets.manifest.json +108 -0
  39. package/dist/assets/docker-entrypoint.sh +15 -0
  40. package/dist/assets/package-lock.json +18407 -0
  41. package/dist/assets/package.json +104 -0
  42. package/dist/assets/project.json +219 -0
  43. package/dist/assets/source.json +5 -0
  44. package/dist/assets/src/core/authz/actor.ts +100 -0
  45. package/dist/assets/src/core/authz/authorize.ts +169 -0
  46. package/dist/assets/src/core/authz/grants.ts +347 -0
  47. package/dist/assets/src/core/authz/policy.ts +281 -0
  48. package/dist/assets/src/core/authz/resource.ts +147 -0
  49. package/dist/assets/src/core/authz/types.ts +164 -0
  50. package/dist/assets/src/core/drain.ts +54 -0
  51. package/dist/assets/src/core/ingressTokens.ts +64 -0
  52. package/dist/assets/src/core/memory/engine.ts +115 -0
  53. package/dist/assets/src/core/memory/scorer.ts +147 -0
  54. package/dist/assets/src/core/memory/types.ts +120 -0
  55. package/dist/assets/src/core/normalizeSpans.ts +299 -0
  56. package/dist/assets/src/core/prDescriptionTypes.ts +54 -0
  57. package/dist/assets/src/core/redact.ts +113 -0
  58. package/dist/assets/src/core/runEvents.ts +537 -0
  59. package/dist/assets/src/core/runFriction.ts +665 -0
  60. package/dist/assets/src/core/runLedger/decisions.ts +126 -0
  61. package/dist/assets/src/core/runLedger/types.ts +177 -0
  62. package/dist/assets/src/core/runRecord.ts +627 -0
  63. package/dist/assets/src/core/runShape.ts +61 -0
  64. package/dist/assets/src/core/schedules.ts +452 -0
  65. package/dist/assets/src/core/time/formatDuration.ts +61 -0
  66. package/dist/assets/src/core/trace/attrs.ts +203 -0
  67. package/dist/assets/src/core/trace/classify.ts +49 -0
  68. package/dist/assets/src/core/trace/clock.ts +6 -0
  69. package/dist/assets/src/core/trace/context.ts +9 -0
  70. package/dist/assets/src/core/trace/ids.ts +23 -0
  71. package/dist/assets/src/core/trace/partition.ts +235 -0
  72. package/dist/assets/src/core/trace/sinks.ts +68 -0
  73. package/dist/assets/src/core/trace/streamSpans.ts +163 -0
  74. package/dist/assets/src/core/trace/traceparent.ts +29 -0
  75. package/dist/assets/src/core/trace/tracer.ts +247 -0
  76. package/dist/assets/src/core/trace/types.ts +125 -0
  77. package/dist/assets/src/core/trace/workerTrace.ts +97 -0
  78. package/dist/assets/src/deploy/buildStamp.ts +93 -0
  79. package/dist/assets/src/deploy/liveGate.ts +203 -0
  80. package/dist/assets/src/deploy/profile.ts +162 -0
  81. package/dist/assets/src/deploy/restart.ts +393 -0
  82. package/dist/assets/src/effort.ts +17 -0
  83. package/dist/assets/src/execution/bashTimeout.ts +78 -0
  84. package/dist/assets/src/execution/bindingPurge.ts +43 -0
  85. package/dist/assets/src/execution/residentBackupTransfer.ts +50 -0
  86. package/dist/assets/src/execution/residentCleanliness.ts +95 -0
  87. package/dist/assets/src/execution/residentCredentials.ts +81 -0
  88. package/dist/assets/src/execution/residentDepCache.ts +321 -0
  89. package/dist/assets/src/execution/residentDepsStore.ts +326 -0
  90. package/dist/assets/src/execution/residentDetach.ts +48 -0
  91. package/dist/assets/src/execution/residentDisk.ts +107 -0
  92. package/dist/assets/src/execution/residentDiskBudget.ts +448 -0
  93. package/dist/assets/src/execution/residentExecWrap.ts +100 -0
  94. package/dist/assets/src/execution/residentHead.ts +85 -0
  95. package/dist/assets/src/execution/residentReadonly.ts +72 -0
  96. package/dist/assets/src/execution/residentRefresh.ts +429 -0
  97. package/dist/assets/src/execution/residentRestoreExtract.ts +130 -0
  98. package/dist/assets/src/execution/residentState.ts +47 -0
  99. package/dist/assets/src/execution/residentStepReport.ts +98 -0
  100. package/dist/assets/src/execution/residentStepTrace.ts +97 -0
  101. package/dist/assets/src/execution/residentSteps.ts +99 -0
  102. package/dist/assets/src/execution/residentText.ts +83 -0
  103. package/dist/assets/src/execution/residentTrace.ts +119 -0
  104. package/dist/assets/src/execution/sandboxEnv.ts +42 -0
  105. package/dist/assets/src/execution/sandboxErrors.ts +159 -0
  106. package/dist/assets/src/execution/sandboxKeepalive.ts +118 -0
  107. package/dist/assets/src/execution/shellQuote.ts +8 -0
  108. package/dist/assets/src/mcp/registry.ts +242 -0
  109. package/dist/assets/src/providers/types.ts +152 -0
  110. package/dist/assets/web/dist/.vite/manifest.json +176 -0
  111. package/dist/assets/web/dist/assets/AppShell-Bk2gbvet.js +1 -0
  112. package/dist/assets/web/dist/assets/CostsPage-CTZcMYYx.js +1 -0
  113. package/dist/assets/web/dist/assets/NotFoundPage-C-BuaSm8.js +1 -0
  114. package/dist/assets/web/dist/assets/ResidentDetailPage-D3shEnzl.js +1 -0
  115. package/dist/assets/web/dist/assets/ResidentsIndexPage-DWIubQ05.js +1 -0
  116. package/dist/assets/web/dist/assets/RunRoutePage-BMjuE-oX.js +126 -0
  117. package/dist/assets/web/dist/assets/RunRoutePage-XVFj0XDc.css +1 -0
  118. package/dist/assets/web/dist/assets/RunsIndexPage-C3_jYIo0.js +1 -0
  119. package/dist/assets/web/dist/assets/RunsTabs-C4krAL9o.js +1 -0
  120. package/dist/assets/web/dist/assets/ScheduledPage-g1W58mtN.js +1 -0
  121. package/dist/assets/web/dist/assets/StatusDot-DcPRw3zu.js +1 -0
  122. package/dist/assets/web/dist/assets/Tooltip-DJUkMYjo.js +1 -0
  123. package/dist/assets/web/dist/assets/favicon-DL1rdWJt.js +1 -0
  124. package/dist/assets/web/dist/assets/localIso-L06jV29p.js +1 -0
  125. package/dist/assets/web/dist/assets/main-BsBGUyMH.css +2 -0
  126. package/dist/assets/web/dist/assets/main-CyM5f4JC.js +28 -0
  127. package/dist/assets/web/dist/assets/residentDiskBudget-BMBKlYRH.js +1 -0
  128. package/dist/assets/web/dist/assets/seed-BglCRKLA.js +6 -0
  129. package/dist/assets/web/dist/assets/wallClock-Ckv3sKoR.js +1 -0
  130. package/dist/cli.js +34494 -0
  131. package/package.json +43 -10
@@ -0,0 +1,120 @@
1
+ {
2
+ "$schema": "node_modules/wrangler/config-schema.json",
3
+ "name": "{{script}}",
4
+ "main": "worker.ts",
5
+ "compatibility_date": "2026-08-01",
6
+ // @cloudflare/sandbox@next needs Node builtins (same as the SDK's own examples).
7
+ "compatibility_flags": ["nodejs_compat"],
8
+ // The installation's Cloudflare account (deploy/profile.json `account`) — every Worker deploys to it.
9
+ "account_id": "{{account}}",
10
+ // Reachable only with the RESIDENT_ADMIN_TOKEN / RESIDENT_OPERATOR_TOKEN
11
+ // bearer secrets (plus an unauthenticated GET /healthz wake ping). Custom
12
+ // domain on the installation's zone so the bot config has a stable URL.
13
+ "workers_dev": false,
14
+ "routes": [{ "pattern": "{{hostname}}", "custom_domain": true }],
15
+ "observability": { "enabled": true },
16
+ "vars": {
17
+ // The state Worker (deploy/cloudflare-memory/): where scheduled() records
18
+ // each resident-watchdog firing. Public URL — a var, not a
19
+ // secret; the bearer is the MEMORY_TOKEN secret (deploy/secrets.manifest.json).
20
+ // A profile without a memory Worker renders no var: firings go unrecorded.
21
+ // {{#if urls.stateWorkerUrl}}
22
+ "STATE_WORKER_URL": "{{urls.stateWorkerUrl}}",
23
+ // {{/if}}
24
+ // Presigned snapshot transfers (docs/reference/specs/resident-repos.md item 61):
25
+ // with these two vars AND the R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY
26
+ // secrets (deploy/secrets.manifest.json) present, the container moves
27
+ // archive bytes itself over presigned R2 URLs and the Durable Object stays
28
+ // out of the data path; any one absent → the SDK's local-bucket mode.
29
+ // BACKUP_BUCKET_NAME MUST equal the BACKUP_BUCKET
30
+ // binding's bucket below — the SDK signs URLs for this name, the offboard
31
+ // sweep deletes through the binding. Public config, not secrets.
32
+ "CLOUDFLARE_ACCOUNT_ID": "{{account}}",
33
+ "BACKUP_BUCKET_NAME": "{{script}}-cache"
34
+ },
35
+ "containers": [
36
+ {
37
+ "class_name": "ResidentDO",
38
+ // The profile's image mode decides (src/deploy/images.ts): `build` renders
39
+ // ./Dockerfile, built by wrangler at deploy time; `registry` renders the
40
+ // release's published image as copied into this account's registry by
41
+ // `deploy images`. The Dockerfile extends docker.io/cloudflare/sandbox:<version>,
42
+ // whose tag MUST match the @cloudflare/sandbox version in package.json
43
+ // EXACTLY (no 'latest' tag exists) — bump both together.
44
+ "image": "{{image}}",
45
+ // Custom 4 vCPU / 12 GiB / 20 GB — the platform's custom-type ceiling
46
+ // (the same shape as standard-4, kept in object form so the sizing test
47
+ // can read it). This ONE type is every resident's container (one DO per
48
+ // repo, one container class), so it is sized for the largest onboarded
49
+ // repo and for 16 threads sharing it. The CPU is the decision now: with
50
+ // one vCPU every thread's test run and the refresh cycle's own install
51
+ // shared a single core (the fifty-concurrent-runs plan, D6). Memory
52
+ // follows the CPU (Cloudflare requires >=3 GiB per vCPU → 12 GiB), and
53
+ // the disk follows the memory (<=2 GB per GiB → 20 GB). The disk
54
+ // arithmetic is over measured parts, checked by instanceSizing.test.ts
55
+ // against this file: the resident of a large monorepo filled the
56
+ // original 8 GB in 45 min (provision, one thread attached 42 min later,
57
+ // ENOSPC 6 min after that). Measured inside the resident
58
+ // (docs/reference/specs/resident-repos.md item 55): image 552 MB + mirror 380
59
+ // + warm checkout 2772 (history 380, tree 94, pnpm node_modules 2298) +
60
+ // the reserve the attach admission holds back (snapshot staging + a
61
+ // 1 GiB floor, 2965) = 6669 MB base; a hardlinked thread tree 555 MB; a
62
+ // thread whose lockfile differs 2853 MB; 20 000 MB mounts as ~18 858 MB
63
+ // usable → 16 hardlinked + 1 installing trees (or 12 + 2) fit; the
64
+ // pool's 16 + 2 does not fit even this ceiling, so the disk stays a
65
+ // BUDGET (residentDiskBudget.ts): the next tree past it is refused
66
+ // `disk-pressure` and runs cold, never ENOSPC. The pnpm store holds no
67
+ // second copy of the deps (measured).
68
+ //
69
+ // Cost: vCPU bills on ACTIVE use only; memory and disk bill on the
70
+ // provisioned size while awake, and a resident is effectively always-on
71
+ // (10-min refresh alarm < 20m sleepAfter) — about $83/mo provisioned per
72
+ // resident (memory ~$79 + disk ~$4) vs ~$55 at 8 GiB / 16 GB, plus vCPU
73
+ // when the cores are busy (up to ~$210/mo with all four pinned, which
74
+ // under load they are meant to be). History: standard-1 (½ vCPU / 4 GiB
75
+ // / 8 GB) → 1 vCPU (a resident pinned at 100% of ½ vCPU, memory fine) →
76
+ // 8 GiB for the 16 GB of disk it unlocks → 4 vCPU / 12 GiB / 20 GB for
77
+ // 16 concurrent seats. That monorepo's console build asks for a 16 GB
78
+ // node heap, so if `build` ever becomes a real command this is still
79
+ // not the ceiling. Custom-type constraints: whole vCPUs 1-4, >=3 GiB per
80
+ // vCPU, <=2 GB disk per GiB, at most 12 GiB / 20 GB. (The thread-sandbox
81
+ // Worker is the predefined standard-4 — the same 4 vCPU / 12 GiB / 20 GB,
82
+ // reached from the other side: one cold thread must be able to typecheck
83
+ // a large monorepo alone — and it is awake only for one run.)
84
+ "instance_type": { "vcpu": 4, "memory_mib": 12288, "disk_mb": 20000 },
85
+ // Platform ceiling only. The enforced cap is RESIDENT_CAP (6) in
86
+ // worker.ts — deliberately below this so an onboard is always refused
87
+ // atomically by the registry, never by a platform scheduling failure.
88
+ "max_instances": 10
89
+ }
90
+ ],
91
+ "durable_objects": {
92
+ "bindings": [
93
+ // One ResidentDO (Sandbox subclass = container) per onboarded resource;
94
+ // the DO name IS the resource id ("repo:<slug>").
95
+ { "name": "RESIDENT", "class_name": "ResidentDO" },
96
+ // Singleton registry: onboarded set + command table + cap enforcement.
97
+ { "name": "REGISTRY", "class_name": "ResidentRegistryDO" }
98
+ ]
99
+ },
100
+ "migrations": [{ "tag": "v1", "new_sqlite_classes": ["ResidentDO", "ResidentRegistryDO"] }],
101
+ // Resident cache + snapshots. The binding name BACKUP_BUCKET is load-bearing:
102
+ // metadata reads and the offboard sweep go through it, and the SDK's
103
+ // local-bucket mode (createBackup({ localBucket: true })) moves the archive
104
+ // bytes through it too — via the Durable Object, which is what put a 128 MB
105
+ // isolate in the data path of a 1.16 GB restore (item 61). Production
106
+ // mode is presigned: the CLOUDFLARE_ACCOUNT_ID / BACKUP_BUCKET_NAME vars
107
+ // above plus the R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY secrets let the
108
+ // container move the bytes itself; with any of the four absent the Worker
109
+ // falls back to local mode and says so on /healthz. The bucket is named after
110
+ // this Worker's script (`<script>-cache`), so two installations on one
111
+ // account never share one — and BACKUP_BUCKET_NAME must say the same name.
112
+ "r2_buckets": [{ "binding": "BACKUP_BUCKET", "bucket_name": "{{script}}-cache" }],
113
+ // Watchdog cadence: every 10 minutes, strictly BELOW the container
114
+ // sleep window (SLEEP_AFTER = "20m" in worker.ts) so a healthy resident is
115
+ // re-warmed before the platform can ever sleep it. Invariant: watchdog/alarm
116
+ // cadence < sleepAfter — bump the two together. This list must equal the
117
+ // `resident` entries of the schedule registry (src/core/schedules.ts) — a
118
+ // unit test enforces it; change the registry first.
119
+ "triggers": { "crons": ["*/10 * * * *"] }
120
+ }
@@ -0,0 +1,67 @@
1
+ # Sandbox container image: the @cloudflare/sandbox base plus git + gh, so the
2
+ # coding/review agents work out of the box.
3
+ # The tag MUST match the @cloudflare/sandbox version in package.json exactly
4
+ # (no 'latest' tag exists) — bump both together.
5
+ FROM docker.io/cloudflare/sandbox:0.12.9
6
+ USER root
7
+ # The SDK's container server kills any command after COMMAND_TIMEOUT_MS. On
8
+ # 0.12.x the default is unlimited (unset → no limit; 0.3.x defaulted to 30s),
9
+ # a per-exec `timeout` option wins over it, and a kill by it lands as a normal
10
+ # result with a synthetic exit code (143/137), not a rejection. worker.ts
11
+ # passes no per-call timeout, so this env var is the container-level lever.
12
+ # Pure BACKSTOP: the real per-command limit is coreutils `timeout` in
13
+ # worker.ts (280s default, raisable per call via the bash tool's timeoutMs up
14
+ # to the 20-min ceiling BASH_TIMEOUT_MAX_MS), which yields a genuine exit 124
15
+ # through the normal result path. This sits 40s above the 20-min ceiling so it
16
+ # never wins the race. Bump alongside BASH_TIMEOUT_MAX_MS
17
+ # (src/execution/bashTimeout.ts); changing it requires a container rebuild +
18
+ # redeploy.
19
+ ENV COMMAND_TIMEOUT_MS=1240000
20
+ # docker.io + iptables: a Docker engine for runs whose tests need containers
21
+ # (act's default job mode, testcontainers, compose, image builds). Nothing
22
+ # starts the daemon at boot — the wrapper below does, on the first `docker`
23
+ # call. The sandbox is already root with full capabilities inside its own
24
+ # microVM, so a container a run starts shares that run's blast radius and
25
+ # nothing more; no privileged flag or capability is added anywhere.
26
+ RUN apt-get update \
27
+ && apt-get install -y --no-install-recommends git curl ca-certificates docker.io iptables \
28
+ && curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
29
+ -o /usr/share/keyrings/githubcli-archive-keyring.gpg \
30
+ && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
31
+ > /etc/apt/sources.list.d/github-cli.list \
32
+ && apt-get update && apt-get install -y --no-install-recommends gh \
33
+ && rm -rf /var/lib/apt/lists/* \
34
+ && git config --system credential.helper '!gh auth git-credential' \
35
+ && git config --system user.name "switchboard-bot" \
36
+ && git config --system user.email "switchboard-bot@users.noreply.github.com"
37
+
38
+ # `docker` resolves to this wrapper (/usr/local/bin precedes /usr/bin on PATH):
39
+ # it starts dockerd in its own session on first use — a plain background
40
+ # process would die with the exec's process group — waits for the engine, then
41
+ # execs the real client. src/deploy/sandboxDocker.test.ts holds its shape.
42
+ COPY --chmod=0755 docker-wrapper.sh /usr/local/bin/docker
43
+
44
+ # Node comes from the base: the published 0.12.9 image ships Node 22.23 (copied
45
+ # from node:22-slim, with npm; no NodeSource deb), so the retarget-and-upgrade
46
+ # layer the 0.3.x image needed is gone. This repo pins `engines: node
47
+ # >=22` (undici@8 needs Node 22's `webidl.util.markAsUncloneable`), and a
48
+ # review run that falls back off the resident onto a fresh sandbox must load
49
+ # the full vitest suite; the assertion fails the build if a base bump ever
50
+ # lands below 22. (The resident image gets Node 24 from its 0.13.0-next base.)
51
+ RUN node --version | grep -Eq '^v(2[2-9]|[3-9][0-9])\.'
52
+
53
+ # pnpm: the base image ships Node + npm (corepack is under
54
+ # /usr/local/lib/node_modules but not on PATH) and not pnpm, so coding runs in pnpm repos hit
55
+ # `pnpm: command not found` on install/build. Installed globally with npm, the
56
+ # same way the resident image does it, so this version is here before any run
57
+ # needs it. It is not necessarily the version that RUNS: pnpm ≥10 honours a
58
+ # repo's own `packageManager` field and fetches it, so a pnpm repo that pins
59
+ # one still needs the registry on its first install.
60
+ #
61
+ # EXACT version, enforced by src/deploy/imagePins.test.ts, same pin and same
62
+ # reason as the resident image (deploy/cloudflare-resident/Dockerfile): with
63
+ # `@latest` here the pnpm major moved on a rebuild, and pnpm 11 stopped
64
+ # reading `package.json`'s `pnpm` field. pnpm still honours a repo's own
65
+ # `packageManager`, so this is the floor for repos that name none.
66
+ RUN npm install -g pnpm@10.34.5 \
67
+ && pnpm --version | grep -qx '10\.34\.5'
@@ -0,0 +1,37 @@
1
+ #!/bin/sh
2
+ # docker(1) for the cold sandbox: starts the engine on first use, then hands
3
+ # over to the real client. Installed as /usr/local/bin/docker, ahead of
4
+ # /usr/bin/docker on PATH, so a run's `docker …` needs no setup step.
5
+ #
6
+ # Why lazy: the image ships dockerd but nothing starts it — the container has
7
+ # no init; the SDK's server only runs the commands it is sent.
8
+ # Why setsid: every /exec runs under `timeout -k 10 … bash -c`, whose process
9
+ # group is reaped when the command returns. A daemon started with `nohup … &`
10
+ # dies with it; one in its own session survives to the next command.
11
+ set -u
12
+ REAL=/usr/bin/docker
13
+ LOG=/var/log/dockerd.log
14
+
15
+ daemon_ready() { "$REAL" info >/dev/null 2>&1; }
16
+
17
+ if [ ! -S /var/run/docker.sock ] || ! daemon_ready; then
18
+ # Containers reach the network through the default bridge, which needs the
19
+ # kernel to forward between it and the outside interface.
20
+ sysctl -w net.ipv4.ip_forward=1 >/dev/null 2>&1 || echo 1 >/proc/sys/net/ipv4/ip_forward
21
+ # Two concurrent first calls both start a daemon; the second exits on the
22
+ # pid lock and both wait below for the one that won.
23
+ setsid -f dockerd >"$LOG" 2>&1
24
+ # The bridge and the socket take a few seconds on a fresh microVM. 40 s is
25
+ # generous, and a broken engine is reported with its log instead of hanging.
26
+ waited=0
27
+ until daemon_ready; do
28
+ waited=$((waited + 1))
29
+ if [ "$waited" -ge 40 ]; then
30
+ echo "docker: the engine did not come up within 40 s — see $LOG" >&2
31
+ exit 1
32
+ fi
33
+ sleep 1
34
+ done
35
+ fi
36
+
37
+ exec "$REAL" "$@"
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "switchboard-sandbox-worker",
3
+ "private": true,
4
+ "type": "module",
5
+ "engines": {
6
+ "node": ">=22"
7
+ },
8
+ "scripts": {
9
+ "check:image": "docker build --quiet .",
10
+ "deploy": "node ../bin/build-stamp.mjs",
11
+ "typecheck": "tsc --noEmit -p tsconfig.json",
12
+ "verify": "npm --prefix ../.. run --silent deploy:gen && npm run typecheck",
13
+ "dev": "wrangler dev",
14
+ "typegen": "wrangler types",
15
+ "tail": "wrangler tail",
16
+ "secrets": "npm --prefix ../.. run --silent cli -- deploy secrets sandbox"
17
+ },
18
+ "dependencies": {
19
+ "@cloudflare/sandbox": "0.12.9"
20
+ },
21
+ "devDependencies": {
22
+ "@cloudflare/workers-types": "^5.20260904.1",
23
+ "typescript": "^5.9.3",
24
+ "wrangler": "^4.129.0"
25
+ }
26
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ // Scoped config so `npx tsc --noEmit` here actually checks worker.ts —
3
+ // without it, tsc silently falls back to the repo-root tsconfig, which
4
+ // excludes this directory entirely.
5
+ "compilerOptions": {
6
+ "target": "esnext",
7
+ "module": "esnext",
8
+ "moduleResolution": "bundler",
9
+ "lib": ["esnext"],
10
+ "types": ["@cloudflare/workers-types"],
11
+ "strict": true,
12
+ "noEmit": true,
13
+ "skipLibCheck": true,
14
+ // worker.ts imports its own package.json for the @cloudflare/sandbox pin
15
+ // (the version onStart compares the container against); wrangler bundles
16
+ // the JSON import, tsc needs this to type it.
17
+ "resolveJsonModule": true
18
+ },
19
+ "include": ["worker.ts"]
20
+ }