@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/dist/network.d.ts
CHANGED
|
@@ -3,21 +3,31 @@ export type EgressMode = 'strict' | 'open';
|
|
|
3
3
|
/** A service whose `url` is an HTTP endpoint (vs a wire-protocol DSN). */
|
|
4
4
|
export declare const isHttpUrl: (u: string) => boolean;
|
|
5
5
|
/**
|
|
6
|
-
* Vendor hostnames the twin answers for
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* Daytona's proxy, which drops anything not allowlisted and forwards the rest
|
|
10
|
-
* to `outboundProxyUrl` — the Veris gateway. So a vendor host that is absent
|
|
11
|
-
* never reaches the gateway and never reaches the twin; it is simply blocked.
|
|
12
|
-
*
|
|
13
|
-
* Allowing it is not a leak, because the allowlist is not what stands between
|
|
14
|
-
* the sandbox and the real vendor — the gateway is. Verified: with every proxy
|
|
15
|
-
* variable stripped, an allowlisted host is still intercepted rather than
|
|
16
|
-
* dialled directly.
|
|
6
|
+
* Vendor hostnames the twin answers for — the ones the gateway intercepts on
|
|
7
|
+
* this sandbox's behalf. Informational: they are what a receipt is about, and
|
|
8
|
+
* they are deliberately NOT handed to Daytona as an allowlist (see buildNetwork).
|
|
17
9
|
*/
|
|
18
10
|
export declare function vendorHosts(services: ServiceInfo[]): string[];
|
|
19
11
|
/** Hosts the twin itself lives at — the proxy must reach these or nothing works. */
|
|
20
12
|
export declare function twinHosts(services: ServiceInfo[]): string[];
|
|
13
|
+
/**
|
|
14
|
+
* The twin hosts a sandbox genuinely cannot work without.
|
|
15
|
+
*
|
|
16
|
+
* A service with vendor routes needs nothing here: the code under test dials
|
|
17
|
+
* `api.stripe.com` and the gateway answers it from the twin. A service with NO
|
|
18
|
+
* routes has no vendor hostname to dial — yente is the one that measured it —
|
|
19
|
+
* so its own twin URL is the only way in, and that URL resolves to a host that
|
|
20
|
+
* was absent from every allowlist we built. Measured: the URL `services()`
|
|
21
|
+
* hands you is unreachable from inside the sandbox, so a routeless twin cannot
|
|
22
|
+
* be used at all.
|
|
23
|
+
*
|
|
24
|
+
* Narrow on purpose, and the narrowness is the point. Every http service of a
|
|
25
|
+
* twin shares ONE hostname (`…/s/<twin>/<service>`), and that hostname also
|
|
26
|
+
* serves `/veris/*` — including `/veris/reset`, which clears the log the
|
|
27
|
+
* receipt is read from. Allowing it is a real cost, so it is paid only when a
|
|
28
|
+
* service would otherwise be unreachable. See the note at the top of state.ts.
|
|
29
|
+
*/
|
|
30
|
+
export declare function directTwinHosts(services: ServiceInfo[]): string[];
|
|
21
31
|
/**
|
|
22
32
|
* Endpoints of non-HTTP data planes (e.g. the pg-gateway a postgres DSN
|
|
23
33
|
* targets). Handed over rather than intercepted, so they need plain
|
|
@@ -36,39 +46,41 @@ export declare function dataPlaneHosts(services: ServiceInfo[]): string[];
|
|
|
36
46
|
*/
|
|
37
47
|
export declare function dataPlaneEnv(services: ServiceInfo[]): Record<string, string>;
|
|
38
48
|
export declare function isSafeEnvName(name: string): boolean;
|
|
39
|
-
/**
|
|
40
|
-
* Package registries and toolchain hosts, allowed by default.
|
|
41
|
-
*
|
|
42
|
-
* A coding sandbox that cannot `npm install` is not a coding sandbox, and
|
|
43
|
-
* registries are not vendors under test, so they must stay reachable. Naming
|
|
44
|
-
* them here keeps the list auditable rather than punching a wildcard.
|
|
45
|
-
*
|
|
46
|
-
* Override wholesale with `veris.allowRegistries: false` plus your own
|
|
47
|
-
* `veris.allowOut`, for a sandbox that should reach nothing but its twin.
|
|
48
|
-
*/
|
|
49
|
-
export declare const DEFAULT_REGISTRY_HOSTS: readonly string[];
|
|
50
49
|
export interface BuildNetworkArgs {
|
|
51
|
-
services: ServiceInfo[];
|
|
52
50
|
mode: EgressMode;
|
|
53
|
-
/**
|
|
54
|
-
*
|
|
55
|
-
|
|
56
|
-
/** Extra hostnames the caller wants reachable. */
|
|
57
|
-
allowOut?: string[];
|
|
58
|
-
/** Include DEFAULT_REGISTRY_HOSTS. Default true. */
|
|
59
|
-
allowRegistries?: boolean;
|
|
51
|
+
/** IPv4 addresses the Veris gateway listens on, from the egress credential
|
|
52
|
+
* (or one DNS lookup of the proxy host on an older control plane). */
|
|
53
|
+
gatewayIps: string[];
|
|
60
54
|
}
|
|
61
55
|
/** The Daytona create params that decide what the sandbox may reach. */
|
|
62
56
|
export interface NetworkParams {
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
networkAllowList?: string;
|
|
58
|
+
}
|
|
59
|
+
/** What buildNetwork decided. */
|
|
60
|
+
export interface NetworkPlan {
|
|
61
|
+
params: NetworkParams;
|
|
65
62
|
}
|
|
66
63
|
/**
|
|
67
|
-
* Strict (the default)
|
|
68
|
-
*
|
|
64
|
+
* Strict (the default) pins the sandbox to the gateway's addresses and nothing
|
|
65
|
+
* else: `networkAllowList` is one /32 per gateway IP. Every hostname the code
|
|
66
|
+
* under test dials — vendor, registry, anything — then travels as a CONNECT
|
|
67
|
+
* through the gateway, which answers vendor hostnames from the twin and passes
|
|
68
|
+
* public hosts through. A client that ignores the proxy variables cannot dial
|
|
69
|
+
* out at all (Daytona blocks it), so it fails closed rather than reaching the
|
|
70
|
+
* real vendor.
|
|
71
|
+
*
|
|
72
|
+
* Why an ADDRESS list and not the hostname list this used to build: measured
|
|
73
|
+
* on Daytona, a `domainAllowList` set beside `outboundProxyUrl` switches its
|
|
74
|
+
* egress proxy into TLS inspection. The client is shown a leaf signed by
|
|
75
|
+
* Daytona's own CA, Daytona opens a second TLS session to the gateway and
|
|
76
|
+
* rejects the Veris-signed leaf it gets back, and every vendor call ends as
|
|
77
|
+
* `502 could not reach upstream host` whatever the sandbox trusts.
|
|
78
|
+
* `networkAllowList` beside the same proxy URL leaves the tunnel untouched.
|
|
79
|
+
* Pinning to the gateway also retires Daytona's 20-domain cap: what may be
|
|
80
|
+
* reached is the gateway's decision, not a list assembled here.
|
|
69
81
|
*
|
|
70
|
-
* Open sets no allowlist at all.
|
|
71
|
-
*
|
|
72
|
-
*
|
|
82
|
+
* Open sets no allowlist at all. Daytona still blocks anything that bypasses
|
|
83
|
+
* the proxy in this mode, so it is not a leak; it is the way to run against a
|
|
84
|
+
* control plane that has not published its gateway addresses.
|
|
73
85
|
*/
|
|
74
|
-
export declare function buildNetwork(args: BuildNetworkArgs):
|
|
86
|
+
export declare function buildNetwork(args: BuildNetworkArgs): NetworkPlan;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export declare const DEFAULT_API_BASE = "https://svc.api.veris.ai";
|
|
2
|
+
/** The profile file's shape, as the CLI writes it (internal/cfg/global.go). */
|
|
3
|
+
export interface ProfileFile {
|
|
4
|
+
active_profile?: string;
|
|
5
|
+
profiles?: Record<string, {
|
|
6
|
+
api_base?: string;
|
|
7
|
+
api_key?: string;
|
|
8
|
+
} | null>;
|
|
9
|
+
}
|
|
10
|
+
/** Where a key and a control plane were finally found. */
|
|
11
|
+
export interface VerisCredentials {
|
|
12
|
+
apiKey?: string;
|
|
13
|
+
apiBase: string;
|
|
14
|
+
/** Which layer answered for the key. `none` when nothing did. */
|
|
15
|
+
keySource: 'option' | 'env' | 'profile' | 'none';
|
|
16
|
+
/** Which layer answered for the base. `default` means nobody named one —
|
|
17
|
+
* which matters to a caller deciding whether the base is a trusted source. */
|
|
18
|
+
baseSource: 'option' | 'env' | 'profile' | 'default';
|
|
19
|
+
/** The profile that was (or would have been) read, and whether it exists. */
|
|
20
|
+
profile: {
|
|
21
|
+
name: string;
|
|
22
|
+
path: string;
|
|
23
|
+
found: boolean;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export interface ResolveOpts {
|
|
27
|
+
/** A key given in code, which wins over everything. */
|
|
28
|
+
apiKey?: string;
|
|
29
|
+
/** A base given in code, which wins over everything. */
|
|
30
|
+
apiBase?: string;
|
|
31
|
+
/** The environment to read; defaults to process.env. */
|
|
32
|
+
env?: NodeJS.ProcessEnv;
|
|
33
|
+
/** The profile file's path; defaults to ~/.veris/twin.yaml. */
|
|
34
|
+
path?: string;
|
|
35
|
+
/** Reads the file, or returns undefined when it does not exist. Injected
|
|
36
|
+
* so the resolution is testable without touching the home directory. */
|
|
37
|
+
readFile?: (path: string) => string | undefined;
|
|
38
|
+
}
|
|
39
|
+
/** ~/.veris/twin.yaml, resolved the way the CLI resolves it. */
|
|
40
|
+
export declare function profilePath(home?: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Parse the profile file. A missing file is not an error: a fresh machine has
|
|
43
|
+
* none, and the caller says what to do about that. A file that exists but
|
|
44
|
+
* does not parse IS an error naming the path, because silently treating it as
|
|
45
|
+
* empty would send the user to log in again over a stray tab — the CLI's own
|
|
46
|
+
* rule, kept so the two agree about the same file.
|
|
47
|
+
*/
|
|
48
|
+
export declare function parseProfileFile(text: string | undefined, path: string): ProfileFile | undefined;
|
|
49
|
+
/** Which profile a command uses: VERIS_PROFILE → active_profile → default. */
|
|
50
|
+
export declare function selectProfileName(env: NodeJS.ProcessEnv, file: ProfileFile | undefined): string;
|
|
51
|
+
/**
|
|
52
|
+
* The key and control plane this process should use, and where each came
|
|
53
|
+
* from. Never throws for a missing key — that is a decision for the caller,
|
|
54
|
+
* which knows whether it needs one — but does throw for a file it cannot read.
|
|
55
|
+
*/
|
|
56
|
+
export declare function resolveVerisCredentials(opts?: ResolveOpts): VerisCredentials;
|
|
57
|
+
/** "VERIS_API_KEY is not set, and …" — the two places a key was looked for. */
|
|
58
|
+
export declare function missingKeyWhere(c: VerisCredentials): string;
|
|
59
|
+
/** "Run `veris login` … or set VERIS_API_KEY" — where a key can come from. */
|
|
60
|
+
export declare const KEY_SOURCES_HINT: string;
|
|
61
|
+
/**
|
|
62
|
+
* What to say when no key was found anywhere: name every place one could
|
|
63
|
+
* have come from, so the fix is on the screen. `veris login` is first because
|
|
64
|
+
* it is the route that needs nothing typed into a shell.
|
|
65
|
+
*/
|
|
66
|
+
export declare function missingKeyMessage(c: VerisCredentials): string;
|
|
67
|
+
/** resolveVerisCredentials, or a MissingCredentialsError that says where a key can come from. */
|
|
68
|
+
export declare function requireVerisCredentials(opts?: ResolveOpts): VerisCredentials & {
|
|
69
|
+
apiKey: string;
|
|
70
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
export interface ProvisionOptions {
|
|
2
|
+
/** The twin to attach to. Required: a provisioned box is only useful pointed
|
|
3
|
+
* at a twin the caller already made. */
|
|
4
|
+
sandbox: string;
|
|
5
|
+
/** Daytona image or snapshot to create the sandbox from. Unset: Daytona's default. */
|
|
6
|
+
image?: string;
|
|
7
|
+
snapshot?: string;
|
|
8
|
+
/** KEY=VALUE pairs set as sandbox environment variables. */
|
|
9
|
+
env: Record<string, string>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Idle minutes before Daytona stops the sandbox.
|
|
13
|
+
*
|
|
14
|
+
* Nothing deletes a provisioned box for the caller, so these two intervals are
|
|
15
|
+
* the only thing standing between an abandoned box and a bill. Daytona's own
|
|
16
|
+
* default is 15 minutes of no API activity; 30 is deliberately more generous,
|
|
17
|
+
* because a caller here is installing dependencies and running a suite between
|
|
18
|
+
* our calls and a box stopped underneath them is worse than half an hour of
|
|
19
|
+
* idle compute.
|
|
20
|
+
*/
|
|
21
|
+
export declare const AUTO_STOP_MINUTES = 30;
|
|
22
|
+
/**
|
|
23
|
+
* Minutes after the sandbox stops before Daytona deletes it.
|
|
24
|
+
*
|
|
25
|
+
* Daytona disables auto-delete by default, so a stopped box keeps its disk (and
|
|
26
|
+
* its cost) forever. An hour is long enough to come back and look at a box that
|
|
27
|
+
* stopped while you were at lunch, short enough that forgetting one is cheap.
|
|
28
|
+
*/
|
|
29
|
+
export declare const AUTO_DELETE_MINUTES = 60;
|
|
30
|
+
/**
|
|
31
|
+
* Wall-clock life of a provisioned sandbox, in minutes.
|
|
32
|
+
*
|
|
33
|
+
* The hard backstop under the two intervals above: it destroys the box whatever
|
|
34
|
+
* state it is in. Four hours fits a long install and a long suite with room to
|
|
35
|
+
* spare, and bounds a box that was never torn down. It does NOT move the twin's
|
|
36
|
+
* TTL — provision always attaches, and the twin's life was decided when whoever
|
|
37
|
+
* created it said so.
|
|
38
|
+
*/
|
|
39
|
+
export declare const PROVISION_TTL_MINUTES = 240;
|
|
40
|
+
/** What `provision` prints on stdout. One object, nothing else there. */
|
|
41
|
+
export interface ProvisionResult {
|
|
42
|
+
/** The Daytona sandbox id — what `veris-daytona teardown` takes. */
|
|
43
|
+
daytonaSandboxId: string;
|
|
44
|
+
/** The Veris twin's id. NOT the Daytona one; the receipt is read from this. */
|
|
45
|
+
verisSandboxId: string;
|
|
46
|
+
/** The Veris environment the twin belongs to. */
|
|
47
|
+
verisEnvironmentId: string;
|
|
48
|
+
/** Whether deleting the sandbox also deletes the twin. Always false here:
|
|
49
|
+
* provision attaches to a twin the caller made, so the caller keeps it. */
|
|
50
|
+
ownsTwin: boolean;
|
|
51
|
+
/** An empty directory in the sandbox to put code in and run commands from. */
|
|
52
|
+
workDir: string;
|
|
53
|
+
/** The CA bundle inside the sandbox: the public roots plus the Veris CA. */
|
|
54
|
+
caBundlePath: string;
|
|
55
|
+
/** The CA trust variables. Export these on EVERY command — Daytona overwrites
|
|
56
|
+
* the well-known ones inside the sandbox with its own CA file, which cannot
|
|
57
|
+
* verify the gateway's certificates. */
|
|
58
|
+
trustEnv: Record<string, string>;
|
|
59
|
+
/** The same variables as one line of shell `export`s, to prefix a command
|
|
60
|
+
* with when you have no env map to fill. */
|
|
61
|
+
trustPrelude: string;
|
|
62
|
+
/** Run this INSIDE the sandbox after installing dependencies, to append the
|
|
63
|
+
* Veris CA to the CA bundles SDKs ship with them. */
|
|
64
|
+
patchBundledCasCommand: string;
|
|
65
|
+
/** How to get code into the box. Daytona's own CLI has no upload, copy or
|
|
66
|
+
* sync command and its `ssh` takes no remote command, so a caller who does
|
|
67
|
+
* not know this verb exists has no route in short of writing an SDK script. */
|
|
68
|
+
pushCommand: string;
|
|
69
|
+
/** How to run one command in the box with trustEnv already exported and its
|
|
70
|
+
* output streaming. `daytona exec` can set no variables at all. */
|
|
71
|
+
execCommand: string;
|
|
72
|
+
/** The twin's service names — what `--require-service` would name. */
|
|
73
|
+
services: string[];
|
|
74
|
+
/** When Daytona destroys the sandbox regardless of state. */
|
|
75
|
+
expiresAt: string;
|
|
76
|
+
/** Idle minutes before Daytona stops the sandbox. */
|
|
77
|
+
autoStopMinutes: number;
|
|
78
|
+
/** Minutes after stopping before Daytona deletes it. */
|
|
79
|
+
autoDeleteMinutes: number;
|
|
80
|
+
}
|
|
81
|
+
export declare const PROVISION_USAGE = "usage: veris-daytona provision --sandbox <twin-id> [options]\n\nCreates a Daytona sandbox whose vendor API calls are answered by an existing\nVeris twin, and stops there: nothing is uploaded, nothing is run, nothing is\ndeleted. One JSON object is printed on stdout; progress goes to stderr.\n\n --sandbox <twin-id> the Veris twin to attach to (required)\n --image <name> Daytona image to run in (default: Daytona's default snapshot)\n --snapshot <name> Daytona snapshot to run in\n --env KEY=VALUE set as a sandbox environment variable (repeatable)\n\nneeds: DAYTONA_API_KEY, and a Veris key: VERIS_API_KEY, or the profile\n`veris login` saved in ~/.veris/twin.yaml (VERIS_PROFILE picks one; the\nenvironment variable wins when both exist). No VERIS_ENVIRONMENT_ID \u2014 the\nenvironment comes from the twin. A Daytona key without delete:sandboxes still\nprovisions, with a warning that teardown will be refused.\n\nthe JSON on stdout:\n daytonaSandboxId the Daytona sandbox id; `veris-daytona teardown` takes it\n verisSandboxId the Veris twin's id \u2014 NOT the Daytona one\n verisEnvironmentId the environment the twin belongs to\n ownsTwin false: the twin is yours, and teardown leaves it alone\n workDir an empty directory to put code in and run commands from\n caBundlePath the CA bundle inside the sandbox (public roots + the Veris CA)\n trustEnv the CA variables, as a map \u2014 export them on EVERY command\n trustPrelude the same variables as one line of shell `export`s\n patchBundledCasCommand run it inside the sandbox after installing dependencies\n pushCommand how to get code in \u2014 the Daytona CLI cannot upload\n execCommand how to run one command in there, trustEnv already applied\n services the twin's service names\n expiresAt when Daytona destroys the sandbox whatever state it is in\n autoStopMinutes idle minutes before Daytona stops it\n autoDeleteMinutes minutes after stopping before Daytona deletes it\n\nGetting code in: the Daytona CLI has no upload, copy or sync command, its `ssh`\ntakes no remote command, and `--context` is a build context on `create` only \u2014\nso `veris-daytona push <daytonaSandboxId>` is the route. It tars the current\ndirectory into workDir. `veris-daytona exec <daytonaSandboxId> -- <command>`\nthen runs a command in there with trustEnv already exported and its output\nstreaming, neither of which `daytona exec` can do.\n\nData-plane variables (DATABASE_URL and the like) are already set inside the\nsandbox, so a command run in there inherits them.\n\nThe sandbox stops after 30 idle minutes, is deleted 60 minutes after\nit stops, and is destroyed at 240 minutes old whatever state it is in \u2014 nothing\nhere deletes it for you. The twin's own TTL is whatever created it.\n\nexit code: 0 when the sandbox is up and the canary answered; 2 otherwise.";
|
|
82
|
+
/** Parse everything after `provision`. Pure; throws UsageError with a human message. */
|
|
83
|
+
export declare function parseProvisionArgs(argv: readonly string[]): ProvisionOptions;
|
|
84
|
+
/**
|
|
85
|
+
* The JSON object, from the handful of facts only a live create() knows.
|
|
86
|
+
*
|
|
87
|
+
* Everything else in it is derived here rather than in cli.ts, so the shape a
|
|
88
|
+
* caller parses is decided in one pure place and tested without an account.
|
|
89
|
+
*/
|
|
90
|
+
export declare function provisionResult(facts: {
|
|
91
|
+
daytonaSandboxId: string;
|
|
92
|
+
verisSandboxId: string;
|
|
93
|
+
verisEnvironmentId: string;
|
|
94
|
+
workDir: string;
|
|
95
|
+
trustEnv: Record<string, string>;
|
|
96
|
+
services: string[];
|
|
97
|
+
expiresAt: string;
|
|
98
|
+
}): ProvisionResult;
|
|
99
|
+
/** The one line of stdout. Indented, because a human reads this too. */
|
|
100
|
+
export declare function provisionJson(result: ProvisionResult): string;
|
package/dist/push.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface PushOptions {
|
|
2
|
+
/** The Daytona sandbox id — `provision`'s daytonaSandboxId. Not the twin's. */
|
|
3
|
+
sandboxId: string;
|
|
4
|
+
/** Git URL to clone into the sandbox. Unset: the current directory is uploaded. */
|
|
5
|
+
repo?: string;
|
|
6
|
+
/** Branch to clone. Only meaningful with --repo. */
|
|
7
|
+
ref?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const PUSH_USAGE: string;
|
|
10
|
+
/** Parse everything after `push`. Pure; throws UsageError with a human message. */
|
|
11
|
+
export declare function parsePushArgs(argv: readonly string[]): PushOptions;
|
package/dist/receipt.d.ts
CHANGED
|
@@ -1,19 +1,31 @@
|
|
|
1
1
|
import type { ServiceInfo } from './control-plane';
|
|
2
2
|
/** One intercepted request, from the twin's trace log. */
|
|
3
3
|
export interface ReceiptRequest {
|
|
4
|
+
/** Row id. Monotonic per service, and what a later read resumes from. */
|
|
5
|
+
id: number;
|
|
4
6
|
method: string;
|
|
5
7
|
path: string;
|
|
6
8
|
/** null = no response sent (fault hang). */
|
|
7
9
|
status: number | null;
|
|
10
|
+
tier: string;
|
|
8
11
|
}
|
|
9
12
|
export interface ReceiptEntry {
|
|
10
|
-
/**
|
|
13
|
+
/** Vendor-surface requests the twin received since the watermark. A floor,
|
|
14
|
+
* not a count, when `capped` is set. */
|
|
11
15
|
requests: number;
|
|
12
16
|
/** The twin service's /veris/* control plane. */
|
|
13
17
|
controlUrl: string;
|
|
14
18
|
/** Typed request list, newest first. */
|
|
15
19
|
entries: ReceiptRequest[];
|
|
16
|
-
/**
|
|
20
|
+
/** The read stopped before the log did, so `requests` is "at least this
|
|
21
|
+
* many". Never silent: a count that is quietly a floor is exactly the bug
|
|
22
|
+
* this replaced. */
|
|
23
|
+
capped: boolean;
|
|
24
|
+
/** Present when this is only a lower bound. */
|
|
25
|
+
incompleteReason?: string;
|
|
26
|
+
sinceId?: number;
|
|
27
|
+
untilId?: number;
|
|
28
|
+
/** The /veris/requests rows read, verbatim as served, merged across pages. */
|
|
17
29
|
raw: unknown;
|
|
18
30
|
}
|
|
19
31
|
export type ReceiptLeak = 'udp-quic-possible' | 'ech-possible';
|
|
@@ -29,8 +41,18 @@ export interface Receipt {
|
|
|
29
41
|
/** Known blind spots of THIS receipt. */
|
|
30
42
|
leaks: ReceiptLeak[];
|
|
31
43
|
}
|
|
44
|
+
export interface RawRow extends ReceiptRequest {
|
|
45
|
+
[key: string]: unknown;
|
|
46
|
+
}
|
|
47
|
+
/** Malformed reads are failures, never successful empty evidence. */
|
|
48
|
+
export declare function rowsOf(body: unknown): RawRow[];
|
|
32
49
|
export declare function parseRequestsBody(body: unknown): {
|
|
33
50
|
count: number;
|
|
34
51
|
entries: ReceiptRequest[];
|
|
52
|
+
total: number;
|
|
35
53
|
};
|
|
36
|
-
export declare function
|
|
54
|
+
export declare function readPage(svc: ServiceInfo, query: URLSearchParams): Promise<RawRow[]>;
|
|
55
|
+
export declare function fetchWatermark(svc: ServiceInfo): Promise<number>;
|
|
56
|
+
/** Read a finite window. The newest-id snapshot also detects servers that
|
|
57
|
+
* silently cap pages below our requested limit. Never subtract row counts. */
|
|
58
|
+
export declare function fetchReceiptEntry(svc: ServiceInfo, sinceId?: number): Promise<ReceiptEntry>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ServiceInfo } from './control-plane';
|
|
2
|
+
export interface ReceiptBaseline {
|
|
3
|
+
version: 1;
|
|
4
|
+
twinId: string;
|
|
5
|
+
sandboxId: string;
|
|
6
|
+
services: Record<string, {
|
|
7
|
+
controlUrl: string;
|
|
8
|
+
id: number;
|
|
9
|
+
marker: string;
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
12
|
+
export declare function captureBaseline(twinId: string, sandboxId: string, services: ServiceInfo[]): Promise<ReceiptBaseline>;
|
|
13
|
+
export declare function validateBaseline(baseline: ReceiptBaseline, twinId: string, sandboxId: string, services: ServiceInfo[]): Promise<void>;
|
package/dist/run.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { Receipt } from './receipt';
|
|
2
|
+
export interface RunOptions {
|
|
3
|
+
/** Git URL to clone into the sandbox. Unset: the current directory is uploaded. */
|
|
4
|
+
repo?: string;
|
|
5
|
+
/** Branch to clone. */
|
|
6
|
+
ref?: string;
|
|
7
|
+
/** Veris environment. Unset: VERIS_ENVIRONMENT_ID. */
|
|
8
|
+
environment?: string;
|
|
9
|
+
/** Attach to an existing twin instead of creating one. It is not deleted afterwards. */
|
|
10
|
+
sandbox?: string;
|
|
11
|
+
/** Daytona image or snapshot to create the sandbox from. Unset: Daytona's default. */
|
|
12
|
+
image?: string;
|
|
13
|
+
snapshot?: string;
|
|
14
|
+
/** Shell command run once before the main command (dependency install). */
|
|
15
|
+
setup?: string;
|
|
16
|
+
/** Services the receipt must show traffic for. Empty: any service will do. */
|
|
17
|
+
requireService: string[];
|
|
18
|
+
/** KEY=VALUE pairs exported to the setup and main commands. */
|
|
19
|
+
env: Record<string, string>;
|
|
20
|
+
/** Keep the sandbox and twin after the run, and print how to reach them. */
|
|
21
|
+
keep: boolean;
|
|
22
|
+
/** Seconds the main command may run. */
|
|
23
|
+
timeoutSeconds: number;
|
|
24
|
+
/** The command itself, everything after `--`. */
|
|
25
|
+
command: string;
|
|
26
|
+
}
|
|
27
|
+
export declare const DEFAULT_TIMEOUT_SECONDS = 1800;
|
|
28
|
+
export declare const USAGE = "usage: veris-daytona run [options] -- <command>\n\nRuns <command> in a Daytona sandbox whose vendor API calls are answered by a\nVeris twin, then prints what the twin received.\n\n --repo <url> git URL to clone into the sandbox (default: upload the current directory)\n --ref <branch> branch to clone\n --environment <id> Veris environment (default: $VERIS_ENVIRONMENT_ID)\n --sandbox <twin-id> attach to an existing twin instead of creating one\n --image <name> Daytona image to run in (default: Daytona's default snapshot)\n --snapshot <name> Daytona snapshot to run in\n --setup <cmd> shell command run first, e.g. 'npm ci' or 'pip install -e .'\n --require-service <name> the receipt must show this service (repeatable; default: any)\n --env KEY=VALUE exported to the setup and main commands (repeatable)\n --timeout <seconds> how long <command> may run (default: 1800)\n --keep leave the sandbox and twin running afterwards\n\nneeds: DAYTONA_API_KEY (with delete:sandboxes, or the sandbox outlives the run),\na Veris key \u2014 VERIS_API_KEY, or the profile `veris login` saved in\n~/.veris/twin.yaml (VERIS_PROFILE picks one) \u2014 and VERIS_ENVIRONMENT_ID (or\n--environment). GITHUB_TOKEN or GH_TOKEN is used for a private --repo.\n\nexit code: the command's; 1 if the twin received nothing (a pass without a receipt is not a pass).";
|
|
29
|
+
export declare class UsageError extends Error {
|
|
30
|
+
}
|
|
31
|
+
/** Parse everything after `run`. Pure; throws UsageError with a human message. */
|
|
32
|
+
export declare function parseRunArgs(argv: readonly string[]): RunOptions;
|
|
33
|
+
/** Quote argv words back into one shell line, leaving plain words alone. */
|
|
34
|
+
export declare function shellJoin(words: readonly string[]): string;
|
|
35
|
+
export interface Verdict {
|
|
36
|
+
/** The process exit code the run should end with. */
|
|
37
|
+
exitCode: number;
|
|
38
|
+
/** One line per reason, for the human. Empty when everything held. */
|
|
39
|
+
problems: string[];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Decide the exit code from the command's own status and the receipt.
|
|
43
|
+
*
|
|
44
|
+
* A failing command fails the run, receipt or not. A passing command still
|
|
45
|
+
* fails the run when the twin saw nothing, or saw nothing for a service the
|
|
46
|
+
* caller required — that is the case the receipt exists to catch.
|
|
47
|
+
*/
|
|
48
|
+
export declare function verdict(commandExitCode: number, receipt: Receipt, requireService: readonly string[]): Verdict;
|
|
49
|
+
/**
|
|
50
|
+
* The receipt as the human sees it.
|
|
51
|
+
*
|
|
52
|
+
* A count the read could not finish prints as "≥N", per service and in the
|
|
53
|
+
* total. The twin's log is read in pages, so a very long run can end at the
|
|
54
|
+
* page budget, and printing that floor as if it were the count would be a
|
|
55
|
+
* wrong number stated confidently — the one thing a receipt must never be.
|
|
56
|
+
*/
|
|
57
|
+
export declare function formatReceipt(receipt: Receipt, twinId: string): string;
|
|
58
|
+
/**
|
|
59
|
+
* The directory under the sandbox's own home that every verb puts code in.
|
|
60
|
+
*
|
|
61
|
+
* One name shared by four verbs on purpose: `provision` prints it as workDir,
|
|
62
|
+
* `push` unpacks into it, `exec` runs in it by default, and `run` does all
|
|
63
|
+
* three. A caller chaining provision → push → exec never has to carry the path
|
|
64
|
+
* between the commands, and cannot get it wrong.
|
|
65
|
+
*/
|
|
66
|
+
export declare const WORK_SUBDIR = "veris-run";
|
|
67
|
+
/** Directories never worth shipping: rebuilt inside the sandbox, or not source. */
|
|
68
|
+
export declare const UPLOAD_EXCLUDES: readonly string[];
|
|
69
|
+
/** Exit status coreutils `timeout` reports when it had to stop the command. */
|
|
70
|
+
export declare const TIMED_OUT_EXIT = 124;
|
|
71
|
+
/**
|
|
72
|
+
* The one shell line a session runs: cd, exports, then the command under
|
|
73
|
+
* coreutils `timeout` so a hung suite is stopped inside the sandbox. Images
|
|
74
|
+
* without `timeout` run the command bare; the client-side backstop still
|
|
75
|
+
* applies.
|
|
76
|
+
*/
|
|
77
|
+
export declare function commandLine(cwd: string, env: Record<string, string>, command: string, timeoutSeconds: number): string;
|
|
78
|
+
export declare function shellQuote(s: string): string;
|
|
79
|
+
/**
|
|
80
|
+
* The environment a command runs with. Daytona overwrites SSL_CERT_FILE,
|
|
81
|
+
* REQUESTS_CA_BUNDLE and CURL_CA_BUNDLE inside the sandbox with its own CA
|
|
82
|
+
* file, which cannot verify the gateway's leaf and cannot be amended (root
|
|
83
|
+
* owned, read-only mount). So the Veris trust variables are exported on every
|
|
84
|
+
* command, where they beat the session's inherited values. The caller's --env
|
|
85
|
+
* comes last and wins.
|
|
86
|
+
*/
|
|
87
|
+
export declare function commandEnv(trust: Record<string, string>, user: Record<string, string>): Record<string, string>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ServiceInfo } from './control-plane';
|
|
2
|
+
export type ControlResource = 'manual' | 'schema' | 'operations' | 'data' | 'requests';
|
|
3
|
+
export interface ControlOptions {
|
|
4
|
+
method?: 'GET' | 'POST' | 'PATCH';
|
|
5
|
+
query?: Record<string, string>;
|
|
6
|
+
body?: unknown;
|
|
7
|
+
}
|
|
8
|
+
export declare function serviceControl(svc: ServiceInfo, resource: ControlResource, options?: ControlOptions): Promise<unknown>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { DaytonaKeyInfo } from './daytona-key';
|
|
2
|
+
export interface TeardownOptions {
|
|
3
|
+
/** The Daytona sandbox id. Not the twin's. */
|
|
4
|
+
sandboxId: string;
|
|
5
|
+
}
|
|
6
|
+
export declare const TEARDOWN_USAGE = "usage: veris-daytona teardown <daytona-sandbox-id>\n\nDeletes the Daytona sandbox. The id is the one `veris-daytona provision`\nprinted as daytonaSandboxId \u2014 not the Veris twin's id.\n\nThe twin follows the rule it was created under: one this package created is\ndeleted with the sandbox, one it attached to (`provision`, or `run --sandbox`)\nis yours and is left running. Which of the two happened is printed.\n\nneeds: DAYTONA_API_KEY with the delete:sandboxes permission \u2014 a key made with\n\"write sandboxes\" alone is refused, and the refusal says when Daytona will stop\nand delete the box on its own. To reach a twin that must be deleted, a Veris\nkey too: VERIS_API_KEY, or the profile `veris login` saved.\n\nexit code: 0 when the sandbox is deleted; 1 when there is no such sandbox, or\nthe key may not delete one.";
|
|
7
|
+
/** The facts a refused delete is described from. Intervals are the sandbox's own. */
|
|
8
|
+
export interface RefusedTeardown {
|
|
9
|
+
/** The Daytona sandbox id. */
|
|
10
|
+
sandboxId: string;
|
|
11
|
+
twinId?: string;
|
|
12
|
+
/** Whether the wrapped delete removed the twin before Daytona refused the
|
|
13
|
+
* sandbox — it deletes the twin first, so an owned twin is already gone. */
|
|
14
|
+
ownsTwin: boolean;
|
|
15
|
+
/** Daytona's `autoStopInterval`: idle minutes before it stops. 0 is off. */
|
|
16
|
+
autoStopMinutes?: number;
|
|
17
|
+
/** Daytona's `autoDeleteInterval`: minutes after stopping before it is
|
|
18
|
+
* deleted. 0 is at once; negative or unset is never. */
|
|
19
|
+
autoDeleteMinutes?: number;
|
|
20
|
+
/** Daytona's `autoDestroyAt`, when a TTL was set. */
|
|
21
|
+
expiresAt?: string;
|
|
22
|
+
/** The key's record, when GET /api/api-keys/current answered. */
|
|
23
|
+
key?: DaytonaKeyInfo;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Was this Daytona's "you may not" — HTTP 403 — rather than any other failure?
|
|
27
|
+
* Read off the status the SDK stamps rather than a class, so the check holds
|
|
28
|
+
* across the SDK's own renamings (DaytonaAuthorizationError became
|
|
29
|
+
* DaytonaForbiddenError) and for an error that crossed a package boundary.
|
|
30
|
+
*/
|
|
31
|
+
export declare function isPermissionDenied(e: unknown): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* What Daytona will do to the box on its own, in one clause: "stops after 30
|
|
34
|
+
* idle minutes and is deleted 60 minutes after it stops, and is destroyed at
|
|
35
|
+
* <time> whatever state it is in". This is the substance of a refusal — the
|
|
36
|
+
* reader's real question is whether the box bills forever, and the answer is
|
|
37
|
+
* on the sandbox object.
|
|
38
|
+
*/
|
|
39
|
+
export declare function sandboxBrakes(r: Pick<RefusedTeardown, 'autoStopMinutes' | 'autoDeleteMinutes' | 'expiresAt'>): string;
|
|
40
|
+
/**
|
|
41
|
+
* What `teardown` says when Daytona answered 403.
|
|
42
|
+
*
|
|
43
|
+
* The bare "FAILED Access denied" this replaces named neither the cause nor the
|
|
44
|
+
* consequence. The cause is a key without `delete:sandboxes` — the one
|
|
45
|
+
* permission DELETE /api/sandbox/<id> checks, and one a key made with "write
|
|
46
|
+
* sandboxes" alone does not have. The consequence is a box that is still there
|
|
47
|
+
* and still billing, so the message says exactly when Daytona will stop and
|
|
48
|
+
* delete it by itself, what happened to the twin, and how to delete it sooner.
|
|
49
|
+
*/
|
|
50
|
+
export declare function teardownRefusedMessage(r: RefusedTeardown): string;
|
|
51
|
+
/** Parse everything after `teardown`. Pure; throws UsageError with a human message. */
|
|
52
|
+
export declare function parseTeardownArgs(argv: readonly string[]): TeardownOptions;
|
package/dist/trust.d.ts
CHANGED
|
@@ -36,6 +36,106 @@ export declare const VERIS_BUNDLE = "/tmp/veris-ca-bundle.crt";
|
|
|
36
36
|
* is the one file that works in both worlds.
|
|
37
37
|
*/
|
|
38
38
|
export declare function vendoredTrustEnv(): Record<string, string>;
|
|
39
|
+
/**
|
|
40
|
+
* The same variables as one POSIX `export` prelude, to prefix a command with.
|
|
41
|
+
*
|
|
42
|
+
* The map above is the right shape when you control the process's environment.
|
|
43
|
+
* A caller that does not — anything handing a command to a shell whose env it
|
|
44
|
+
* did not build — has nowhere to put a map, and the command inherits Daytona's
|
|
45
|
+
* overwritten SSL_CERT_FILE / REQUESTS_CA_BUNDLE / CURL_CA_BUNDLE /
|
|
46
|
+
* NODE_EXTRA_CA_CERTS instead. Measured with the inherited value: `uv sync`
|
|
47
|
+
* dies with "invalid peer certificate: UnknownIssuer". Prefixing this makes
|
|
48
|
+
* the command's own shell put the Veris bundle back:
|
|
49
|
+
*
|
|
50
|
+
* sandbox.process.executeCommand(`${trustPrelude()} ${yourCommand}`)
|
|
51
|
+
*
|
|
52
|
+
* Values are single-quoted, so the string stays one safe command line whatever
|
|
53
|
+
* the control plane served.
|
|
54
|
+
*/
|
|
55
|
+
export declare function trustPrelude(env?: Record<string, string>): string;
|
|
56
|
+
/**
|
|
57
|
+
* CA bundles that SDKs ship INSIDE the code under test, as slash-anchored path
|
|
58
|
+
* suffixes.
|
|
59
|
+
*
|
|
60
|
+
* No trust variable reaches these. stripe-python passes
|
|
61
|
+
* `verify=stripe.ca_bundle_path` explicitly, so all eighteen variables above
|
|
62
|
+
* are irrelevant to it and the first Stripe call dies with "Could not verify
|
|
63
|
+
* Stripe's SSL certificate" — the gateway's forged leaf is signed by a CA that
|
|
64
|
+
* file has never heard of. Appending our CA to the file keeps the SDK on the
|
|
65
|
+
* code path that ships; the bundle merely holds one more root.
|
|
66
|
+
*
|
|
67
|
+
* The table is the veris CLI's (internal/bundlescan), for the reason it gives
|
|
68
|
+
* there: matched as a path SUFFIX because site-packages prefixes vary per
|
|
69
|
+
* image and the tail does not, and deliberately with no bare `cacert.pem`
|
|
70
|
+
* rule, because that filename also names test fixtures and client-auth
|
|
71
|
+
* material that must not quietly gain a root.
|
|
72
|
+
*/
|
|
73
|
+
export declare const BUNDLED_CA_FILES: readonly {
|
|
74
|
+
sdk: string;
|
|
75
|
+
suffix: string;
|
|
76
|
+
}[];
|
|
77
|
+
/** Where the patch script is written inside the sandbox, so anything that can
|
|
78
|
+
* run a shell command — a test harness, an agent, a person over ssh — can
|
|
79
|
+
* re-run it after installing dependencies without holding this SDK. */
|
|
80
|
+
export declare const BUNDLED_CA_PATCH_SCRIPT = "/tmp/veris-patch-bundled-cas.sh";
|
|
81
|
+
/**
|
|
82
|
+
* What the script prints per patched file, so the caller can report what
|
|
83
|
+
* happened rather than infer it from an exit code.
|
|
84
|
+
*
|
|
85
|
+
* A sentence rather than a machine marker, because both routes to this script
|
|
86
|
+
* are read by a person. `provision` tells a caller to run `sh
|
|
87
|
+
* /tmp/veris-patch-bundled-cas.sh` themselves, and whatever the script prints
|
|
88
|
+
* is what they see — a bare `__VERIS_PATCHED__ /path` reads as debug output
|
|
89
|
+
* that escaped. `patchBundledCas()` parses the paths back off this same
|
|
90
|
+
* constant, and cli.ts prints the same sentence for the SDK route, so the
|
|
91
|
+
* prose a human reads and the list a program gets cannot drift apart.
|
|
92
|
+
*/
|
|
93
|
+
export declare const BUNDLED_CA_PATCHED_MARKER = "the Veris CA was appended to ";
|
|
94
|
+
/**
|
|
95
|
+
* The script that finds those bundles and appends the Veris CA to each.
|
|
96
|
+
*
|
|
97
|
+
* Meant to run AFTER dependencies are installed, and to be re-run: the bundles
|
|
98
|
+
* do not exist at create time, and an agent installs more of them mid-session,
|
|
99
|
+
* so there is no create-time moment that covers either. Idempotent by
|
|
100
|
+
* construction — a file already carrying our certificate is skipped, matched
|
|
101
|
+
* on a line of its base64 body rather than on the BEGIN line every certificate
|
|
102
|
+
* shares.
|
|
103
|
+
*
|
|
104
|
+
* Appending in place, where the CLI over-mounts a patched copy: a Daytona
|
|
105
|
+
* sandbox offers no bind mounts and the container is disposable, so editing
|
|
106
|
+
* the file is the equivalent move. Unwritable files are skipped rather than
|
|
107
|
+
* sudo'd — a root-owned bundle in the system Python is rarely the one the
|
|
108
|
+
* application's virtualenv reads, and silently rewriting system trust is a
|
|
109
|
+
* larger act than this warrants.
|
|
110
|
+
*/
|
|
111
|
+
export declare function bundledCaPatchScript(): string;
|
|
112
|
+
/**
|
|
113
|
+
* Node is the one runtime the trust variables above cannot reach.
|
|
114
|
+
*
|
|
115
|
+
* Daytona overwrites NODE_EXTRA_CA_CERTS and SSL_CERT_FILE inside the sandbox
|
|
116
|
+
* with its own CA file — right for clients that go through its proxy, which
|
|
117
|
+
* terminates TLS with that CA. Node is not such a client: it ignores
|
|
118
|
+
* HTTPS_PROXY, is forwarded to the gateway end to end, and the leaf it
|
|
119
|
+
* validates is signed by the Veris CA, which Daytona's file lacks. Seen live:
|
|
120
|
+
* plain `node` failed against a vendor host with UNABLE_TO_VERIFY_LEAF_SIGNATURE.
|
|
121
|
+
*/
|
|
122
|
+
/**
|
|
123
|
+
* Node's trust flag. NODE_EXTRA_CA_CERTS is Daytona's, and Node's baked-in
|
|
124
|
+
* Mozilla roots know neither CA — so switch Node to OpenSSL's store:
|
|
125
|
+
* --use-openssl-ca loads SSL_CERT_FILE (Daytona's file) AND the system
|
|
126
|
+
* certificate directory, where CA_INSTALL_CMD puts our CA and the distribution
|
|
127
|
+
* keeps the public roots. Injected as NODE_OPTIONS, which Daytona leaves alone.
|
|
128
|
+
*
|
|
129
|
+
* Verified live on Daytona's default image (uid 1001, passwordless sudo,
|
|
130
|
+
* update-ca-certificates present): 200 from plain `node` with the flag and
|
|
131
|
+
* our CA in /etc/ssl/certs; UNABLE_TO_VERIFY_LEAF_SIGNATURE without the flag.
|
|
132
|
+
* So this leans on the store install succeeding. There is no second route:
|
|
133
|
+
* Daytona's CA file is on a read-only mount, so appending our CA to it — the
|
|
134
|
+
* obvious alternative — fails for root too.
|
|
135
|
+
*/
|
|
136
|
+
export declare const NODE_TRUST_FLAG = "--use-openssl-ca";
|
|
137
|
+
/** NODE_OPTIONS with the trust flag appended to whatever the caller set. */
|
|
138
|
+
export declare function nodeOptionsWithTrust(existing?: string): string;
|
|
39
139
|
/**
|
|
40
140
|
* The store-based install, for stacks that read a trust store rather than an
|
|
41
141
|
* env var — a Java client honours no CA variable at all. Rebuilds the system
|