sandbox

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 14, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

Documentation

Overview

Package sandbox is the lifecycle manager for the local Korbit API Sandbox: it runs the single-file mock under Deno — the CLI-managed pinned Deno by default, or a system `deno` — handing Deno the Official-Source bundle URL to fetch and cache itself, runs/stops/inspects it as a managed background server, and imports the seeded ED25519 key into the normal keystore so a user goes from nothing to a signed, working local exchange in one command. It is a deliberately limited convenience feature: there is no Node runtime and the CLI does not manage a bundle cache. For anything more involved, download the bundle from the Official Source and run it yourself.

Design boundaries this package enforces:

  • It NEVER writes config.json (the global default is untouched) and only ever writes LOOPBACK base URLs onto the sandbox key. So a sandbox run can't redirect real commands, and a real command can't silently hit the mock.
  • The seeded key is imported via keys.AddBound so its SANDBOX_ id is present at creation and it is never even transiently the default.
  • The artifact cache (the managed Deno + Deno's own module cache) is shared across agents; mutable state (db, pidfile, log) lives under KORBIT_CLI_HOME/sandbox/.

Index

Constants

View Source
const (
	// PrecheckPrefix prefixes a fatal runtime-precheck line (it carries the exact
	// upgrade command, surfaced verbatim rather than second-guessed by the CLI).
	PrecheckPrefix = "SANDBOX_PRECHECK fatal:"
	// NotInitializedSubstr appears in the "db is not initialized" message.
	NotInitializedSubstr = "is not initialized"
	// AlreadyRunningSubstr appears in the "may already be running" message.
	AlreadyRunningSubstr = "may already be running"
	// SchemaMismatchSubstr appears in the schema-version-mismatch message.
	SchemaMismatchSubstr = "has schema version"
	// VersionTooOldPrefix prefixes the refusal the bundle prints when it is older
	// than MinVersionEnv requires. It is checked before config load, so on that
	// path the bundle's only output is its version (stdout) and this line
	// (stderr). The manager detects the refusal by scanning for this prefix (the
	// same output-classification approach as the other startup failures), so it
	// still works for the detached `run` whose numeric exit it captures to the log.
	VersionTooOldPrefix = "SANDBOX_VERSION_TOO_OLD:"
)

Stable stderr prefixes the bundle prints. The CLI scans run.log for them to classify a startup failure and surface a precise message.

View Source
const (

	// MinVersionEnv tells the bundle the lowest version this CLI supports; an
	// older bundle refuses to start (printing its version + VersionTooOldPrefix),
	// so the manager can update + retry. Set on the start invocations (init-db /
	// run) unless the version check is skipped. The name's KORBIT_SANDBOX_ prefix
	// keeps it inside the bundle run's --allow-env allowlist.
	MinVersionEnv = "KORBIT_SANDBOX_MIN_VERSION"
	// LicenseCmdEnv overrides the command the bundle's banner footer names for the
	// full terms, so a re-surfaced banner points at `<prog> sandbox license`
	// instead of the standalone bundle invocation. Same allowlisted prefix.
	LicenseCmdEnv = "KORBIT_SANDBOX_LICENSE_CMD"
)

Environment variables the manager sets on the bundle for a managed run.

View Source
const DefaultKeyName = "sandbox"

DefaultKeyName is the imported seeded key's name.

View Source
const DefaultPort = 9999

DefaultPort is the predictable default the sandbox binds, falling back to an OS-assigned ephemeral port (via the bundle's --port 0) on collision.

View Source
const DefaultSandboxURL = "https://docs.korbit.co.kr/korbit-sandbox.mjs"

DefaultSandboxURL is the Official Source the sandbox bundle is obtained from. It is the only place the bundle should ever come from; a local path / file:// URL is also accepted (offline/dev use) via the URL override. Under the Deno runtime the bundle source is passed straight to `deno run`, which fetches and caches it; the CLI does not download or cache the bundle itself.

View Source
const MinSandboxVersion = "1.2.0"

MinSandboxVersion is the lowest sandbox bundle version this CLI supports. It is passed to the bundle (via MinVersionEnv) on start; an older bundle refuses to run, so the CLI can update + retry rather than fail confusingly against a build missing a capability it relies on. Bump this in lockstep when the CLI starts depending on a newer bundle feature. The pre-release version-skew guard (repo-root scripts/) asserts this never exceeds the bundle's own version.

Variables

This section is empty.

Functions

func ResolveCacheDir

func ResolveCacheDir(getenv func(string) string) (string, error)

ResolveCacheDir resolves the shared artifact cache root: KORBIT_CLI_SANDBOX_CACHE if set, else os.UserCacheDir()/korbit-cli. It is decoupled from the CLI home so an ephemeral per-agent home doesn't re-download the heavy artifacts, and it is the single resolver both `sandbox` (which fills the cache) and `self uninstall` (which can remove it) share, so the two can't drift onto different directories. getenv reads the process environment (injectable for tests).

func StateDir

func StateDir(home string) string

StateDir is the per-home mutable state root (KORBIT_CLI_HOME/sandbox): the db (+ its -wal/-shm/-pid sidecars) and run.log. Exported so a caller cleaning up after the sandbox (e.g. `self uninstall`) removes the same directory this manager writes, without duplicating the "sandbox" subdir name.

Types

type Config

type Config struct {
	// Home is the CLI home (KORBIT_CLI_HOME); mutable state lives under it.
	Home string
	// CacheDir is the shared artifact cache root (os.UserCacheDir()/korbit-cli,
	// or KORBIT_CLI_SANDBOX_CACHE).
	CacheDir string
	// URL overrides the bundle source (Official Source by default; a local path
	// or file:// is read from disk).
	URL string
	// RuntimePref is "", "deno", or "managed-deno".
	RuntimePref string
	// Port is the requested fixed port (0 ⇒ DefaultPort with ephemeral fallback;
	// a non-zero value forces that exact port with no fallback).
	Port int
	// DB overrides the database path (default <home>/sandbox/korbit-sandbox.db).
	DB string
	// KeyName overrides the imported key's name (default DefaultKeyName).
	KeyName string
	// Reimport replaces the imported key's material even if it already exists.
	Reimport bool
	// Paper enables paper trading: a fresh database is initialized with its
	// seeded pairs' market data mirrored LIVE from production Korbit (the
	// bundle's `init-db --source live`, which verifies each pair's production
	// status and needs network); fills stay simulated locally. The seeded set is
	// the bundle's fixture pairs by default, or every LAUNCHED production pair
	// (the tradable universe) when AllPairs is set. On an existing database the
	// flag only VERIFIES the pairs
	// are already in live mode — it never flips a database in place (see
	// ensurePaperDB); combine with Fresh to switch modes.
	Paper bool
	// AllPairs seeds a fresh database from a live production snapshot (the
	// bundle's `init-db --mode korbit-api`) so every LAUNCHED production pair
	// exists (the tradable universe), instead of the bundle's built-in fixture
	// pairs. Orthogonal to Paper: with Paper it mirrors those pairs LIVE, without
	// it they are on the simulated market. The snapshot is cached beside the db
	// (see marketCachePath), so a repeated (re)seed reuses it instead of
	// refetching. Affects initialization only — an existing database keeps its
	// pairs; combine with Fresh to reseed. Needs network.
	AllPairs bool
	// Fresh starts from a brand-new database: any running server for it is
	// stopped first, then the database and its sidecars are deleted before the
	// normal bring-up initializes a new one (in the mode Paper selects).
	// Destructive by design — balances, orders, and trades are discarded; the
	// seeded keys are deterministic, so the imported key stays valid.
	Fresh bool
	// PassThrough are extra args forwarded to the bundle's `run` (e.g.
	// --history-lag-ms), after the CLI-managed ones.
	PassThrough []string
	// SkipVersionCheck disables the min-version gate on start (the bundle is run
	// without MinVersionEnv), so a known-older bundle can still be started.
	SkipVersionCheck bool
}

Config carries one sandbox operation's settings. Zero values resolve to the documented defaults.

type Deps

type Deps struct {
	// Doer downloads the bundle and the managed Deno, and probes /v2/time.
	Doer Doer
	// Now is the clock (UnixMilli).
	Now func() int64
	// Log receives one-line USER-FACING progress notes (downloads, the resolved
	// command line, reuse) — program output the CLI routes to a Note, NOT a
	// level-gated log. Kept as a func(string) seam.
	Log func(string)
	// Logger receives structured operational DIAGNOSTICS about the lifecycle —
	// runtime resolution, the resolved command line, detached spawn pid, readiness
	// polling, pidfile reads, the reuse/spawn decision, the port-collision
	// fallback, stop/kill, and the version gate. nil = silent (logging.Or). It is
	// distinct from Log: Log is program output, Logger is --debug telemetry.
	Logger *slog.Logger
	// LookPath resolves an executable (exec.LookPath); tests stub it to control
	// whether a system `deno` appears present.
	LookPath func(string) (string, error)
	// DenoURL / DenoVersion override the managed-Deno pin (KORBIT_CLI_DENO_*).
	DenoURL     string
	DenoVersion string
	// KeyManager imports the seeded key; the caller wires it over KORBIT_CLI_HOME.
	KeyManager *keys.Manager
	// BannerOut, if set, receives the short license notice at the top of `start`
	// (rendered by the bundle's own `license --show-banner`, so the text is never
	// reproduced in the CLI). Wired to the CLI's stderr; nil disables the banner.
	BannerOut io.Writer
}

Deps are the injectable dependencies, so the manager runs under test with a fake runtime, stub HTTP client, and fixed clock.

type Doer

type Doer interface {
	Do(*http.Request) (*http.Response, error)
}

Doer performs an HTTP request; *http.Client satisfies it. Used to download the managed Deno binary and to probe the sandbox's /v2/time readiness endpoint.

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager performs sandbox operations for one Config.

func New

func New(cfg Config, deps Deps) *Manager

New builds a Manager, applying defaults.

func (*Manager) Exec

func (m *Manager) Exec(ctx context.Context, args []string, stdout, stderr io.Writer) error

Exec passes args straight through to the bundle under the resolved Deno runtime against the managed db, streaming stdout/stderr to the given writers. It avoids re-implementing the data-poke subcommands (set-balance, add-pair, …).

func (*Manager) License

func (m *Manager) License(ctx context.Context, extra []string, stdout, stderr io.Writer) error

License runs the bundle's `license` subcommand and streams its output through. Unlike Exec it injects NO --db (license opens no database) — it just needs the bundle resolved (and fetched, if remote). extra carries any pass-through flags (e.g. --lang ko, or --show-banner for just the short notice); with none the bundle follows the locale (LANG/LC_*). LicenseCmdEnv is set so the banner footer names this CLI's own `<prog> sandbox license`, not the standalone bundle invocation.

func (*Manager) RuntimeInstall

func (m *Manager) RuntimeInstall(ctx context.Context) (deno.Status, error)

RuntimeInstall ensures the managed Deno is present (download on first use).

func (*Manager) RuntimeStatus

func (m *Manager) RuntimeStatus() deno.Status

RuntimeStatus reports the managed-Deno cache status (no network call).

func (*Manager) RuntimeUpdate

func (m *Manager) RuntimeUpdate(ctx context.Context) (deno.Status, error)

RuntimeUpdate forces a re-download of the pinned managed Deno.

func (*Manager) Start

func (m *Manager) Start(ctx context.Context) (StartResult, error)

Start ensures the bundle+runtime, initializes the db if needed, spawns the bundle's `run` detached, polls /v2/time for readiness, then imports and (re)pins the seeded Ed25519 key. The bound port comes from the pidfile (the single source of truth), so a --port 0 collision fallback transparently updates the summary and the key pin.

func (*Manager) Status

func (m *Manager) Status(ctx context.Context) StatusResult

Status assembles the runtime-free status report. It does not download anything or start a runtime — pure inspection over the cache, pidfile, and a TCP probe.

func (*Manager) Stop

func (m *Manager) Stop(ctx context.Context) (StopResult, error)

Stop reads the pidfile, SIGTERMs the process, and waits for it to exit (the bundle removes its own pidfile on a clean shutdown). It is runtime-free.

func (*Manager) Update

func (m *Manager) Update(ctx context.Context) (StatusResult, error)

Update re-fetches the bundle from the Official Source into Deno's module cache (`deno cache --reload`, so a stale cached bundle is replaced). It is the license seam (a consent gate drops in here later).

type Pidfile

type Pidfile struct {
	PID  int `json:"pid"`
	Port int `json:"port"`
}

Pidfile is the JSON the bundle writes next to its db file (at <db>-pid) once it binds, and removes on a clean exit. The CLI reads the actual bound port back from here (the single source of truth for the summary and the key pin).

type Runtime

type Runtime struct {
	Kind RuntimeKind
	// contains filtered or unexported fields
}

Runtime is a resolved way to invoke the bundle: `<deno> run <least-privilege perms> <bundleURLorPath> <args>` (see Manager.denoRunPerms). The bundle source (URL or local path) is handed straight to Deno, which fetches+caches it.

type RuntimeKind

type RuntimeKind string

RuntimeKind selects which Deno runs the bundle. There is no Node runtime: the sandbox is a deliberately limited convenience feature and runs only under Deno (for its permission sandbox). For anything more involved, download the bundle from the Official Source and run it yourself.

const (
	// RuntimeSystemDeno runs the bundle with a `deno` already on PATH.
	RuntimeSystemDeno RuntimeKind = "deno"
	// RuntimeManagedDeno runs it with the CLI-managed, pinned Deno (downloaded on
	// first use). It is the default — a known, checksum-verified version.
	RuntimeManagedDeno RuntimeKind = "managed-deno"
)

type ServerState

type ServerState struct {
	Running bool `json:"running"`
	PID     int  `json:"pid,omitempty"`
	Port    int  `json:"port,omitempty"`
	// Reachable is the result of a /v2/time probe against the bound port.
	Reachable bool `json:"reachable"`
}

ServerState describes whether a sandbox is running for this home's db.

type StartResult

type StartResult struct {
	PID         int    `json:"pid"`
	Port        int    `json:"port"`
	RestBaseURL string `json:"restBaseUrl"`
	WSBaseURL   string `json:"wsBaseUrl"`
	KeyName     string `json:"keyName"`
	APIKeyID    string `json:"apiKeyId"`
	Runtime     string `json:"runtime"`
	Bundle      string `json:"bundle"`
	DB          string `json:"db"`
	LogPath     string `json:"logPath"`
	Imported    bool   `json:"imported"`
	// Paper reports the started database's ACTUAL mode, read from the bundle's
	// status — not merely whether --paper was passed. True when the database
	// was initialized for paper trading (`init-db --source live`), or when
	// every configured pair mirrors live production market data.
	Paper bool `json:"paper"`
	// WalkPairs lists the pairs still on the simulated walk while Paper is true
	// — pairs production reported non-launched when the database was
	// initialized, so their market data is not mirrored. Omitted from the JSON
	// (not an empty array) for a fully-live paper database and when Paper is
	// false.
	WalkPairs []string `json:"walkPairs,omitempty"`
	// PairCount is the total number of configured pairs.
	PairCount int `json:"pairCount"`
	// Recreated reports that --fresh deleted an existing database before the
	// start (previous balances/orders/trades were discarded). False when
	// --fresh found no database to delete.
	Recreated bool `json:"recreated"`
}

StartResult reports a completed `sandbox start`.

type StatusResult

type StatusResult struct {
	CachePath    string      `json:"cachePath"`
	BundleCached bool        `json:"bundleCached"`
	SourceURL    string      `json:"sourceUrl,omitempty"`
	Runtime      string      `json:"runtime"`
	DenoPath     string      `json:"denoPath,omitempty"`
	DenoStatus   deno.Status `json:"deno"`
	Server       ServerState `json:"server"`
	DB           string      `json:"db"`
	KeyName      string      `json:"keyName"`
	KeyImported  bool        `json:"keyImported"`
}

StatusResult is the runtime-free `sandbox status` report: bundle source, the resolved runtime + the Deno binary that would run it, the server state, and the imported-key state.

type StopResult

type StopResult struct {
	Stopped bool `json:"stopped"`
	PID     int  `json:"pid"`
	Port    int  `json:"port"`
}

StopResult reports a `sandbox stop`.

Directories

Path Synopsis
Package deno manages a pinned, checksum-verified Deno runtime that korbit-cli downloads on demand to run the local API sandbox.
Package deno manages a pinned, checksum-verified Deno runtime that korbit-cli downloads on demand to run the local API sandbox.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL