@veris-ai/daytona 0.2.1 → 0.3.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/README.md +254 -14
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +2099 -0
- package/dist/cli.js.map +1 -0
- package/dist/control-plane.d.ts +13 -5
- package/dist/daytona-key.d.ts +28 -0
- package/dist/daytona.d.ts +86 -10
- package/dist/exec.d.ts +15 -0
- package/dist/gateway.d.ts +35 -1
- package/dist/index.cjs +579 -109
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +13 -5
- package/dist/index.js +554 -108
- package/dist/index.js.map +1 -1
- package/dist/network.d.ts +50 -38
- package/dist/profile.d.ts +70 -0
- package/dist/provision.d.ts +100 -0
- package/dist/push.d.ts +11 -0
- package/dist/receipt.d.ts +25 -3
- package/dist/run-receipt.d.ts +13 -0
- package/dist/run.d.ts +87 -0
- package/dist/service-control.d.ts +8 -0
- package/dist/teardown.d.ts +52 -0
- package/dist/trust.d.ts +100 -0
- package/dist/veris-api.d.ts +58 -0
- package/package.json +7 -1
package/README.md
CHANGED
|
@@ -20,8 +20,8 @@ Both, because `@daytona/sdk` is a peer dependency — that is what keeps
|
|
|
20
20
|
|
|
21
21
|
| variable | where from |
|
|
22
22
|
|---|---|
|
|
23
|
-
| `DAYTONA_API_KEY` | [app.daytona.io/dashboard/keys](https://app.daytona.io/dashboard/keys) |
|
|
24
|
-
| `VERIS_API_KEY` | [studio.veris.ai](https://studio.veris.ai) |
|
|
23
|
+
| `DAYTONA_API_KEY` | [app.daytona.io/dashboard/keys](https://app.daytona.io/dashboard/keys), with the **write and delete sandboxes** permissions — a write-only key provisions boxes it can never delete |
|
|
24
|
+
| `VERIS_API_KEY` | [studio.veris.ai](https://studio.veris.ai) — or leave it unset after `veris login`: the CLI's profile in `~/.veris/twin.yaml` is read instead, `VERIS_PROFILE` picks one, and the variable wins whenever both exist |
|
|
25
25
|
| `VERIS_ENVIRONMENT_ID` | a Veris environment — it decides which vendor services your twin gets |
|
|
26
26
|
|
|
27
27
|
## Use
|
|
@@ -45,6 +45,148 @@ await sbx.delete() // deletes the twin too
|
|
|
45
45
|
This package re-exports everything from `@daytona/sdk`, so it is the only import
|
|
46
46
|
you need to change. No particular sandbox image is required.
|
|
47
47
|
|
|
48
|
+
### Or skip the code: `veris-daytona run`
|
|
49
|
+
|
|
50
|
+
The same four steps as one command, for a test suite that already exists:
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
npx @veris-ai/daytona run --setup 'pip install -e .' -- pytest tests/integration
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
That uploads the current directory into a fresh sandbox (minus `node_modules`,
|
|
57
|
+
`.venv`, `.git` and the like), runs the setup command, runs the test command
|
|
58
|
+
streaming its output, prints the receipt, and deletes the sandbox and the twin.
|
|
59
|
+
The exit code is the test command's — except that a green suite whose twin
|
|
60
|
+
received nothing exits 1, because a pass without a receipt is not a pass.
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
Veris receipt — twin sbx_a1b2c3
|
|
64
|
+
interception: gateway integrity: verified
|
|
65
|
+
|
|
66
|
+
1 request(s) reached the twin:
|
|
67
|
+
stripe: 1 request(s)
|
|
68
|
+
POST /v1/customers -> 200
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
| flag | |
|
|
72
|
+
|---|---|
|
|
73
|
+
| `--repo <url> [--ref <branch>]` | clone instead of uploading; `GITHUB_TOKEN` is used for a private repo |
|
|
74
|
+
| `--environment <id>` | instead of `VERIS_ENVIRONMENT_ID` |
|
|
75
|
+
| `--require-service <name>` | the receipt must show this service, repeatable |
|
|
76
|
+
| `--image <name>` / `--snapshot <name>` | what to run in; default is Daytona's default snapshot |
|
|
77
|
+
| `--env KEY=VALUE` | exported to both commands, repeatable |
|
|
78
|
+
| `--timeout <seconds>` | for the test command; default 1800 |
|
|
79
|
+
| `--keep` | leave the sandbox up afterwards, and the twin if `run` made it |
|
|
80
|
+
|
|
81
|
+
`veris-daytona run --help` lists everything.
|
|
82
|
+
|
|
83
|
+
### Or hand the wired box to something else: `provision`, `push`, `exec`, `teardown`
|
|
84
|
+
|
|
85
|
+
`run` does the whole job in one command, and it stays. But when the thing that
|
|
86
|
+
installs the dependencies and runs the suite is another tool — the `veris` CLI,
|
|
87
|
+
a CI step, an agent — what you want from this package is the first half only:
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
box=$(npx @veris-ai/daytona provision --sandbox sbx_a1b2c3 --image python:3.12)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
That creates a sandbox attached to a twin you already have, does every
|
|
94
|
+
Veris-shaped thing — the egress credential, the gateway pin, the outbound
|
|
95
|
+
proxy, the CA bundle, the canary, the trust variables — and
|
|
96
|
+
stops. Nothing is uploaded, nothing is run, nothing is deleted. One JSON object
|
|
97
|
+
goes to stdout and every human line to stderr, so `$box` is parseable:
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"daytonaSandboxId": "e2a1…",
|
|
102
|
+
"verisSandboxId": "sbx_a1b2c3",
|
|
103
|
+
"verisEnvironmentId": "env_9f…",
|
|
104
|
+
"ownsTwin": false,
|
|
105
|
+
"workDir": "/home/daytona/veris-run",
|
|
106
|
+
"caBundlePath": "/tmp/veris-ca-bundle.crt",
|
|
107
|
+
"trustEnv": { "SSL_CERT_FILE": "/tmp/veris-ca-bundle.crt", "…": "…" },
|
|
108
|
+
"trustPrelude": "export SSL_CERT_FILE='/tmp/veris-ca-bundle.crt'; …",
|
|
109
|
+
"patchBundledCasCommand": "sh /tmp/veris-patch-bundled-cas.sh",
|
|
110
|
+
"pushCommand": "veris-daytona push e2a1…",
|
|
111
|
+
"execCommand": "veris-daytona exec e2a1… -- <command>",
|
|
112
|
+
"services": ["stripe", "github"],
|
|
113
|
+
"expiresAt": "2026-09-04T12:00:00.000Z",
|
|
114
|
+
"autoStopMinutes": 30,
|
|
115
|
+
"autoDeleteMinutes": 60
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
| flag | |
|
|
120
|
+
|---|---|
|
|
121
|
+
| `--sandbox <twin-id>` | the twin to attach to — **required**; `veris up` prints its id |
|
|
122
|
+
| `--image <name>` / `--snapshot <name>` | what to run in; default is Daytona's default snapshot |
|
|
123
|
+
| `--env KEY=VALUE` | set as a sandbox environment variable, repeatable |
|
|
124
|
+
|
|
125
|
+
From there the box is yours. Reading the receipt and deciding what it proved is
|
|
126
|
+
the caller's job; that is the whole point of the split.
|
|
127
|
+
|
|
128
|
+
### Getting code in and running it: `push` and `exec`
|
|
129
|
+
|
|
130
|
+
Daytona has no route into a box that already exists. Their CLI (v0.210.0) has no
|
|
131
|
+
upload, copy or sync command; `daytona ssh` takes exactly one argument, so there
|
|
132
|
+
is no `tar | ssh` and no scp or rsync behind it; `--context` is a Docker build
|
|
133
|
+
context that only exists on `create`, which `provision` owns; and `git clone`
|
|
134
|
+
inside the box is a clone, not an upload of what is on your disk. So two verbs
|
|
135
|
+
do it:
|
|
136
|
+
|
|
137
|
+
```sh
|
|
138
|
+
id=$(echo "$box" | jq -r .daytonaSandboxId)
|
|
139
|
+
|
|
140
|
+
npx @veris-ai/daytona push "$id" # tars the cwd into workDir
|
|
141
|
+
npx @veris-ai/daytona exec "$id" -- pip install -e .
|
|
142
|
+
npx @veris-ai/daytona exec "$id" -- sh /tmp/veris-patch-bundled-cas.sh
|
|
143
|
+
npx @veris-ai/daytona exec "$id" -- python -m pytest tests/integration
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`push` uploads the current directory, minus what gets rebuilt inside
|
|
147
|
+
(`node_modules`, `.venv`, `dist`, `__pycache__`, …), and unpacks it in the same
|
|
148
|
+
`workDir` the JSON named — so the two chain without carrying the path between
|
|
149
|
+
them. `--repo <url> --ref <branch>` clones instead, using `GITHUB_TOKEN` for a
|
|
150
|
+
private one.
|
|
151
|
+
|
|
152
|
+
`exec` runs one command with `trustEnv` already exported, which is the part that
|
|
153
|
+
matters: `daytona exec` has no `--env` flag at all, so a command run through it
|
|
154
|
+
inherits Daytona's own CA file and fails on the gateway's certificate unless you
|
|
155
|
+
retype the trust prelude every single time. It streams output as it happens
|
|
156
|
+
rather than returning at the end, takes `--cwd`, repeatable `--env KEY=VALUE`
|
|
157
|
+
and `--timeout <seconds>`, and exits with the command's own status.
|
|
158
|
+
|
|
159
|
+
Neither reads a receipt or passes a verdict — take a watermark before and read
|
|
160
|
+
`veris sandbox trace --since` after. And once the dependencies are installed,
|
|
161
|
+
run `patchBundledCasCommand` **inside the box** to patch the CA bundles an SDK
|
|
162
|
+
ships with it; the script is already in there, so a shell caller needs nothing
|
|
163
|
+
from this package.
|
|
164
|
+
|
|
165
|
+
### Taking it back: `teardown`
|
|
166
|
+
|
|
167
|
+
However it went:
|
|
168
|
+
|
|
169
|
+
```sh
|
|
170
|
+
npx @veris-ai/daytona teardown "$(echo "$box" | jq -r .daytonaSandboxId)"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
`teardown` deletes the sandbox and says what it did about the twin: one this
|
|
174
|
+
package created goes with it, one it attached to — always the case after
|
|
175
|
+
`provision` — is yours and is left running. It exits 1 when there is no such
|
|
176
|
+
sandbox, saying plainly that no twin was touched.
|
|
177
|
+
|
|
178
|
+
Nothing deletes a provisioned box for you, so it comes up with its own brakes:
|
|
179
|
+
it stops after 30 idle minutes, Daytona deletes it an hour after that, and it
|
|
180
|
+
is destroyed 4 hours after creation whatever state it is in. The twin's TTL is
|
|
181
|
+
untouched — it belongs to whoever created the twin.
|
|
182
|
+
|
|
183
|
+
Deleting needs a Daytona key with the `delete:sandboxes` permission, and a key
|
|
184
|
+
made with "write sandboxes" alone does not have it. `teardown` then exits 1
|
|
185
|
+
saying so: which key, which permissions it has, that the box is still there,
|
|
186
|
+
when its own brakes stop and delete it, what happened to the twin, and where a
|
|
187
|
+
key that can delete comes from. `provision` and `run` read the key's
|
|
188
|
+
permissions first and warn about the same thing *before* creating a box.
|
|
189
|
+
|
|
48
190
|
### Why `assertTouched` and not just a green suite
|
|
49
191
|
|
|
50
192
|
A test suite that skipped its integration and one that exercised it look
|
|
@@ -62,21 +204,54 @@ when it is empty.
|
|
|
62
204
|
| `services()` | what the twin answers for, and where |
|
|
63
205
|
| `manual(service)` | that service's manual: what it models, how its data is shaped |
|
|
64
206
|
| `getDataPlaneEnv()` | `{ DATABASE_URL: … }` for non-HTTP twin services |
|
|
65
|
-
| `getTrustEnv()` | the CA variables
|
|
207
|
+
| `getTrustEnv()` | the CA variables, as a map for a process you start |
|
|
208
|
+
| `trustPrelude()` | the same variables, as one line of shell `export`s |
|
|
209
|
+
| `patchBundledCas()` | append the Veris CA to the CA bundles your SDKs ship |
|
|
210
|
+
| `environmentId` | the Veris environment the twin was deployed from |
|
|
66
211
|
| `deliverTo(port \| url)` | point vendor webhooks back at your sandbox |
|
|
67
212
|
|
|
68
213
|
`sbx.verisSandboxId` is the twin's id — not to be confused with `sbx.id`, which
|
|
69
214
|
is Daytona's.
|
|
70
215
|
|
|
216
|
+
### Trust, for a command you run yourself
|
|
217
|
+
|
|
218
|
+
Daytona overwrites `SSL_CERT_FILE`, `REQUESTS_CA_BUNDLE`, `CURL_CA_BUNDLE` and
|
|
219
|
+
`NODE_EXTRA_CA_CERTS` inside the sandbox with its own CA file, which cannot
|
|
220
|
+
verify the gateway's certificates. Anything not started with the right values
|
|
221
|
+
inherits the broken ones — `uv sync` dies with `invalid peer certificate:
|
|
222
|
+
UnknownIssuer`. Two shapes, because callers come in two shapes:
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
// You control the process's environment:
|
|
226
|
+
await sbx.process.executeCommand('uv sync', cwd, sbx.veris.getTrustEnv())
|
|
227
|
+
|
|
228
|
+
// You can only prefix a command line (a session, someone else's runner):
|
|
229
|
+
await sbx.process.executeCommand(`${sbx.veris.trustPrelude()} uv sync`)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
And for an SDK that reads no variable because it ships its own CA file — the
|
|
233
|
+
measured case is stripe-python's `verify=stripe.ca_bundle_path`:
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
await sbx.process.executeCommand('pip install -e .', cwd, sbx.veris.getTrustEnv())
|
|
237
|
+
console.log(await sbx.veris.patchBundledCas()) // ['…/stripe/data/ca-certificates.crt', …]
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Call it *after* installing dependencies — the bundles arrive with them. It is
|
|
241
|
+
idempotent and returns only the files it changed. `veris-daytona run` calls it
|
|
242
|
+
for you, between `--setup` and the command; a sandbox from `provision` carries
|
|
243
|
+
the same patcher as a script at `/tmp/veris-patch-bundled-cas.sh`, so whoever
|
|
244
|
+
installed the dependencies can run it with no SDK in hand.
|
|
245
|
+
|
|
71
246
|
## Options
|
|
72
247
|
|
|
73
248
|
```ts
|
|
74
249
|
await daytona.create({
|
|
75
250
|
image: 'node:22',
|
|
76
251
|
veris: {
|
|
77
|
-
egress: 'strict', // default
|
|
78
|
-
|
|
79
|
-
|
|
252
|
+
egress: 'strict', // default: Daytona reaches only the gateway's address.
|
|
253
|
+
// 'open': no Daytona allowlist, for a control plane
|
|
254
|
+
// that has not published the gateway's addresses
|
|
80
255
|
installCa: true, // default
|
|
81
256
|
ttlMinutes: 60,
|
|
82
257
|
attachSandboxId: 'sbx_…', // reuse an existing twin; delete() will not remove it
|
|
@@ -90,15 +265,21 @@ Coordinates can also come from `veris.apiKey` / `veris.environmentId` /
|
|
|
90
265
|
|
|
91
266
|
## How it works
|
|
92
267
|
|
|
93
|
-
Every sandbox is created with two Daytona parameters: a `
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
268
|
+
Every sandbox is created with two Daytona parameters: a `networkAllowList`
|
|
269
|
+
holding the Veris gateway's IPv4 address as one `/32` and nothing else, and an
|
|
270
|
+
`outboundProxyUrl` pointing at that gateway over HTTP CONNECT.
|
|
271
|
+
|
|
272
|
+
Daytona chains them: sandbox traffic reaches Daytona's own proxy, which forwards
|
|
273
|
+
it to the gateway, which answers vendor hostnames from the twin and passes
|
|
274
|
+
public hosts — registries, git, a routeless twin's own URL — through untouched.
|
|
275
|
+
A process that ignores the proxy variables cannot dial out at all. Nothing of
|
|
276
|
+
ours runs inside the sandbox, which is why any image works.
|
|
97
277
|
|
|
98
|
-
Daytona
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
278
|
+
Daytona is pinned by address, never by a hostname list, because a
|
|
279
|
+
`domainAllowList` set beside the proxy URL turns Daytona's egress into a
|
|
280
|
+
TLS-inspecting proxy that rejects the gateway's certificate (measured; see the
|
|
281
|
+
repository README). The address comes from the control plane's egress
|
|
282
|
+
credential (`gateway_ips`); an older control plane gets one DNS lookup.
|
|
102
283
|
|
|
103
284
|
Before `create()` resolves, a canary probe dials a reserved hostname only the
|
|
104
285
|
gateway answers, whose body carries the twin id. It proves in one request that
|
|
@@ -122,7 +303,66 @@ four systems involved refused:
|
|
|
122
303
|
cannot be used; `create()` then fails at `credential-mint` saying so.
|
|
123
304
|
- **QUIC/HTTP3 and ECH are not intercepted.** The gateway relays TCP. Both are
|
|
124
305
|
reported in the receipt's `leaks` rather than silently omitted.
|
|
306
|
+
- **The image needs `curl`.** The canary probe runs it; a slim image without
|
|
307
|
+
it fails `create()` in the `canary` phase.
|
|
308
|
+
- **Python 3.13+ needs a gateway that mints strict-verifier-safe leaves**
|
|
309
|
+
(services-sandbox#1044). Without it, `requests` fails with
|
|
310
|
+
`Missing Authority Key Identifier` while `curl` and Node succeed.
|
|
311
|
+
- **Daytona overwrites `REQUESTS_CA_BUNDLE`, `SSL_CERT_FILE` and
|
|
312
|
+
`CURL_CA_BUNDLE`** with its own CA file, which lacks the Veris CA. `run`
|
|
313
|
+
exports the Veris bundle on every command it runs; a command run through
|
|
314
|
+
`sandbox.process` yourself needs `sbx.veris.getTrustEnv()` as its env, or
|
|
315
|
+
`sbx.veris.trustPrelude()` in front of the command line.
|
|
316
|
+
- **An SDK that bundles its own CA reads no variable at all.**
|
|
317
|
+
`sbx.veris.patchBundledCas()` covers certifi, pip's vendored certifi,
|
|
318
|
+
botocore, stripe and httplib2; anything else fails with its own error naming
|
|
319
|
+
the file to add.
|
|
320
|
+
- **Strict mode needs the gateway's address.** It is read from the egress
|
|
321
|
+
credential (`gateway_ips`), else resolved once from the proxy host. When
|
|
322
|
+
neither yields an IPv4 address, `create()` fails at `credential-mint` naming
|
|
323
|
+
`veris.egress: 'open'`, which sets no Daytona allowlist and still blocks a
|
|
324
|
+
process that bypasses the proxy.
|
|
325
|
+
- **`teardown` needs `delete:sandboxes` on the Daytona key.** A write-only
|
|
326
|
+
key creates boxes it cannot delete; the refusal says when Daytona's own
|
|
327
|
+
auto-stop and auto-delete will, and `provision` warns before creating one.
|
|
328
|
+
- **A very long run's receipt is a floor.** The twin's log is read in pages up
|
|
329
|
+
to a budget; past it, `entry.capped` is true and `run` prints `≥N`.
|
|
125
330
|
|
|
126
331
|
## License
|
|
127
332
|
|
|
128
333
|
Apache-2.0. Source: [veris-ai/veris-daytona](https://github.com/veris-ai/veris-daytona).
|
|
334
|
+
|
|
335
|
+
## Receipts for an individual test run
|
|
336
|
+
|
|
337
|
+
The next release adds a baseline API; existing `receipt()` calls still read the
|
|
338
|
+
SDK's default receipt window. Capture immediately before the isolated command:
|
|
339
|
+
|
|
340
|
+
```js
|
|
341
|
+
const baseline = await sandbox.veris.receiptBaseline()
|
|
342
|
+
// Run and await the application's own test command using this provider's SDK.
|
|
343
|
+
const receipt = await sandbox.veris.receiptSince(baseline, 'stripe')
|
|
344
|
+
const entry = receipt.services.stripe
|
|
345
|
+
if (!entry || entry.capped) throw new Error('Application evidence is incomplete')
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
`ReceiptRequest.id` is stable within service history. `receiptSince` validates the
|
|
349
|
+
execution sandbox, twin, service set/control URLs, and a uniquely marked schema
|
|
350
|
+
read retained in the trace both before and after paging. Reset/erasure/replacement
|
|
351
|
+
invalidates the baseline. Services without retained control request headers cannot
|
|
352
|
+
establish this baseline; the call fails explicitly. Numeric IDs alone cannot detect
|
|
353
|
+
all resets. Use a fresh baseline after reconnect/reset, before re-executing the test.
|
|
354
|
+
|
|
355
|
+
Pages advance IDs within a newest-ID snapshot. `entry.requests` is an observed lower
|
|
356
|
+
bound when `capped` is true; `incompleteReason` explains page budget, failed read or
|
|
357
|
+
non-progress. A failed first read throws, while a successful empty window returns
|
|
358
|
+
zero. `sinceId`/`untilId` identify the observed window. Control/probe tiers and
|
|
359
|
+
`/veris/*` paths are excluded from application entries. Unmarked vendor probes and
|
|
360
|
+
concurrent runs remain indistinguishable: finish them before baseline capture and
|
|
361
|
+
retain application response/state assertions. Preserve mode, integrity and leaks.
|
|
362
|
+
|
|
363
|
+
`veris.control(service, resource, options)` supports `manual`, `schema`,
|
|
364
|
+
`operations`, `data`, and `requests`; `options` contains `method`, `query`, and
|
|
365
|
+
`body`. Only data supports `POST`/`PATCH` writes, using the service's schema-defined
|
|
366
|
+
`{data: {table: [rows]}}` envelope, including fault rows. Coordinates come from the
|
|
367
|
+
attached twin; lifecycle and arbitrary URLs are excluded. SDK callers own write
|
|
368
|
+
authorization; the OpenCode plugin applies its configured write permission.
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function main(argv: readonly string[]): Promise<number>;
|