@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/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. These MUST be on the allowlist.
7
- *
8
- * That reads backwards until you follow the path: the sandbox's traffic goes to
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
- /** The Veris gateway's host, and the canary hostname it answers on. Without
54
- * these the sandbox cannot reach the gateway at all. */
55
- gatewayHosts: string[];
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
- networkBlockAll?: boolean;
64
- domainAllowList?: string;
57
+ networkAllowList?: string;
58
+ }
59
+ /** What buildNetwork decided. */
60
+ export interface NetworkPlan {
61
+ params: NetworkParams;
65
62
  }
66
63
  /**
67
- * Strict (the default) is deny-all-except: the vendor hosts the twin answers
68
- * for, the gateway itself, the twin's data planes, and package registries.
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. It exists for debugging and is never the
71
- * default: with no allowlist there is nothing forcing traffic at the gateway,
72
- * and the receipt cannot tell you what slipped past.
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): NetworkParams;
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
- /** Count of intercepted requests (real JSON parse, not a regex). */
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
- /** Verbatim /veris/requests body. */
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 fetchReceiptEntry(svc: ServiceInfo): Promise<ReceiptEntry>;
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