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
- func ResolveCacheDir(getenv func(string) string) (string, error)
- func StateDir(home string) string
- type Config
- type Deps
- type Doer
- type Manager
- func (m *Manager) Exec(ctx context.Context, args []string, stdout, stderr io.Writer) error
- func (m *Manager) License(ctx context.Context, extra []string, stdout, stderr io.Writer) error
- func (m *Manager) RuntimeInstall(ctx context.Context) (deno.Status, error)
- func (m *Manager) RuntimeStatus() deno.Status
- func (m *Manager) RuntimeUpdate(ctx context.Context) (deno.Status, error)
- func (m *Manager) Start(ctx context.Context) (StartResult, error)
- func (m *Manager) Status(ctx context.Context) StatusResult
- func (m *Manager) Stop(ctx context.Context) (StopResult, error)
- func (m *Manager) Update(ctx context.Context) (StatusResult, error)
- type Pidfile
- type Runtime
- type RuntimeKind
- type ServerState
- type StartResult
- type StatusResult
- type StopResult
Constants ¶
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.
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.
const DefaultKeyName = "sandbox"
DefaultKeyName is the imported seeded key's name.
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.
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.
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 ¶
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 ¶
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 ¶
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 (*Manager) Exec ¶
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 ¶
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 ¶
RuntimeInstall ensures the managed Deno is present (download on first use).
func (*Manager) RuntimeStatus ¶
RuntimeStatus reports the managed-Deno cache status (no network call).
func (*Manager) RuntimeUpdate ¶
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.
type Pidfile ¶
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`.