Volter World

Config

.volter/world.json is the committed, portable intent for a World. volter world init writes format 2; up accepts format 2 and legacy unversioned files. Resolved ports, process identities, minted values and timestamps belong to the ignored instance record. Live credentials never belong here.

{
  "schemaVersion": 2,
  "metadata": { "id": "acme-web" },
  "discovery": {
    "selection": {
      "include": [{ "usage": "application" }],
      "exclude": []
    }
  },
  "runtime": {
    "isolation": "colocated",
    "environment": {
      "values": { "GITHUB_TOKEN": "twin-fake-github-token" },
      "strip": ["HOST_SECRET_*"]
    }
  },
  "services": [
    {
      "id": "github",
      "type": "twin",
      "source": { "package": "@volter/twin-github", "version": "2.0.0" },
      "execution": { "colocate": { "export": "createGithubTwinServer" } },
      "endpoint": { "port": "auto" },
      "bindings": { "injectEnv": "GITHUB_TWIN_URL" }
    }
  ]
}

The document

fieldmeaning
schemaVersionRequired integer 2. It versions this document, independently of twin protocol and package versions.
metadataRequired { id, description? }. id is the World name and main branch.
discovery.selectionSaved rules used by both initialization and coverage.
servicesRequired ordered array. Service ids are unique; order is startup order. Empty is valid.
runtime.isolationprocess (default), colocated, or worker.
runtime.environment.valuesVariables given to services and run. $mint creates a structurally valid throwaway value at boot. $issue:<service> is a credential that twin issues from its credential endpoint once it is up (left unset, with a message, when it issues none; see data and keys). $app-url is the application's own origin (its base URL, its auth URL): a command given the World's env gets the URL volter-world app-url <world> answers (what its booter recorded with --set, or the World's own app service), and the name is left unset, with a note, when none is recorded; init writes it for such names. An empty value is left unset, so the app's own env files provide it; strip keeps a caller's variable out, and a service's own execution.environment.values can still set one empty.
runtime.environment.stripCaller variables kept outside: exact names, prefixes ending in *, or *. Declared World values still apply.
runtime.network.egressCanonical exact HTTPS origins, exact paths, or subtree paths ending in /. /css2 grants only that path; /artifacts/ grants descendants at that exact scheme, host and port. Paths decode once and resolve dot segments; encoded separators, malformed/nested escapes and backslashes are refused. Queries and fragments do not influence matching. An empty list refuses external requests through mediated HTTP/Fetch and guarded Node socket seams; sandbox mode applies the same cooperative refusal to untwinned destinations. Twin routing takes precedence. Native DNS, Bun native connectors/aliases, curl and addons require an enforced Machine boundary; see transport coverage. Omission preserves existing routing behavior. This grants no ingress.
runtime.network.phaseruntime by default; build labels a separately owned build World's lifetime from up through the build command to down --purge. The runtime does not infer a program's purpose.
runtime.network.passthroughOptional exact-origin list, requiring phase: build and the same origins explicitly in egress. Only these broad grants permit opaque TLS; paths cannot be listed. Other granted HTTP/global Fetch requests use the World's per-request proxy. Raw sockets, HTTP/2 and unmediated undici cannot use inspected grants; guest inspected egress is refused.
runtime.network.ownLoopbacktrue by default: a run may reach the listeners it opened itself on loopback, when it declares them (an image's EXPOSE, or a listener a host process opened under the injector). false refuses them. A parent World's false carries to its children. See capability decisions.
serving{ mode?, name?, share? }. Mode is app by default. bare requires name: "<org>/<world>".
remotesWorld name to URL or path. origin is the default; tokens are stored separately.
changesets{ approvals?, summary? }: how this World's changesets are made and readied. approvals, a whole number from 0 to 10 (1 when omitted), is how many different people must approve a changeset's current contents before a person deploys it (a key's approval counts for the person it was made for; a vendor whose deploy policy is auto deploys a push at once, without one). summary is required (the default: whoever cuts says what it does and why) or generated (a cut with none takes the summary generated from its changes). Changed by a person or the World's token through the rules endpoint, never by a key.
scenarioOpaque { actors?, fixtures? } metadata recorded for tests; it does not mutate twins.
provenance.catalog{ sha, protocol? }: the catalog birth stamp written by init.

Unknown structural fields fail validation. At the document or service level, keys beginning with // are comments; nested sections do not accept comment keys. User-keyed maps and scenario values are not treated as structural fields.

Selection

Selection contains include and exclude arrays. A selector has usage, vendor, or both:

{
  "include": [
    { "usage": "application" },
    { "usage": "deployment", "vendor": "cloudflare" }
  ],
  "exclude": [{ "usage": "dependencies" }]
}

Usages are application, dependencies, build, and deployment. Fields within one selector must all match; selectors within a list are alternatives; exclusion wins. A vendor is selected when at least one detected use survives. The default selects application uses only, and init writes that default so later runs reuse it. A vendor-only selector addresses all uses of that vendor. Native connection rows also match their protocol vendor (redis selects redis:REDIS_URL); an exclusion on the protocol or precise row wins. Registry destinations are dependency use; twin-declared tools such as Wrangler carry their declared build or deployment use.

Selection controls automatic proposals and coverage obligations. It does not authorize network access, expose credentials, remove explicitly declared services, or excuse broken routing. An excluded finding remains visible as excluded, never covered.

Regeneration keeps saved service bindings, comments and authored infrastructure stubs. Conflicting new endpoint claims fail before writing files. Existing seed.ts files retain their contents and ordering; init reports the imports and calls to add for newly introduced default seeds.

Services

Every service has id and an explicit type: twin, process, or external.

fieldmeaning
descriptionHuman-readable wiring rationale; ignored by the runtime.
source.packageA twin package such as @volter/twin-github, resolved from the World root.
source.versionPackage constraint: exact or ^major.minor. It also applies to a command-backed checkout twin.
execution.process{ command?, args?, rootArg?, portArg? }. Package twins derive their command and may append args.
execution.colocate{ module?, export, scenarioPath? }: the twin factory used by colocated or worker isolation.
execution.lifecycleExternal service { up, status?, down, readyWhen? }.
execution.cwdWorking directory relative to the World root.
execution.environment.valuesVariables for this service only.
execution.preloadExtra Node preloads. Relative paths resolve from the effective service cwd.
execution.controlPlaneWorld infrastructure: receives declared values but not app-side egress machinery.
execution.branchState"unsupported" refuses World branching before child compute; ordinary stopped-state checkout and reset retain their existing lifecycle.
endpoint{ port?, portReason?, ready? } for a World-owned listener. A numeric port requires portReason; otherwise use auto.
bindings.injectEnvExport the service URL under one variable, such as GITHUB_TWIN_URL.
bindings.injectEnvTemplatesMore variables computed from ${url}, ${httpUrl}, ${host}, or ${port}.
bindings.cliRedirectVariables honored by the real vendor CLI, computed from the same templates.
bindings.discoverExternal lifecycle output mappings: { as, protocol?, source?, jsonPath? } or { as, protocol?, source?, pattern? }. protocol (postgres, mysql, mongodb, redis, http) is the wire the discovered connection speaks, so coverage binds it to the application's driver; init writes it for the World-managed infrastructure.
rootA twin's real-system root, described below.

A twin has exactly one of source.package or execution.process.command; it may additionally declare execution.colocate. A process requires a command and cannot declare twin source, colocation, external lifecycle, or root. An external service requires lifecycle up and down, uses bindings.discover for outputs, and cannot declare source, process, colocation, endpoint, preload, or root. Invalid combinations fail before anything starts.

Under colocated or worker isolation, a twin declared outside the host starts beside the host's startup: its co-located twins' addresses are in its environment already, but their ports open only when the host is ready. Such a twin contacts them only to answer a request, never while it starts. A service that must reach one while it starts is declared process and starts after the host is ready (ADR 0015).

A twin root

root identifies the real vendor account behind a twin on a shared World. The credential is sealed separately under .volter/credentials/.

fieldmeaning
urlThe vendor API or a served World's twin URL.
scopeThe one resource the account represents, when the twin requires it.
deployauto, gated, or hold; defaults to gated.
refresh{ every?, webhook? }: scheduled or webhook refresh posture.

Sharing

serving.share retains the existing share shape: provider is cloudflare-quick or command; a custom provider supplies command and optional args; ephemeral records URL lifetime; and services is an array of { id, verifyPath? } targets declared in this World.

Migration

volter-world migrate-config .volter/world.json

Migration accepts only unversioned format 1. It refuses unknown legacy fields, creates the byte-for-byte backup world.json.v1.bak, validates a temporary format-2 file, then replaces the manifest atomically. It preserves service order, path-resolution behavior, roots, remotes, scenario data, comments and provenance. The old all-signals coverage behavior is materialized as all four included usages; changing to the application-only default is a separate edit. Deprecated resources metadata is retained only in the backup and reported as dropped. Ordinary reads never migrate a file, and running instance records are never rewritten.

The World directory

Beside world.json, committed files include handlers/<vendor>.json, seeds/story.ts, and checks/*.ts. Running state is ignored: worlds/<branch>/, world.env, token, credentials/, and current.

The instance record stores actual service URLs, pids, logs, data directories, resolved twin versions, the live environment, and the selection snapshot used at boot. Coverage of a running World uses that snapshot; editing the manifest does not retroactively change it.

Explicit build grants take precedence over twin routing for their matching public paths. Redirects return to the client; each subsequent request is authorized separately.

Without a container runtime, World-managed infrastructure serves a declared PostgreSQL with the machine's own PostgreSQL wherever it has one. The tool directory (postgres, initdb, psql, createdb) comes from VOLTER_WORLD_POSTGRES_BIN, or else from the directory of a postgres server on PATH. A World keeps the database server its retained data was made by. Where no server is installed it is PGlite, and the infrastructure log names PGlite's one shared session. Nothing installs PostgreSQL, and VOLTER_WORLD_INFRA_BACKING=native|pglite|docker forces one. Native PostgreSQL is the installed version with the installation's extensions, and it keeps machine time. It serves the declared PostgreSQL through independent sessions in a private loopback-only cluster, retained on ordinary down and owned by the World lifecycle. It reads the generated definition's literal POSTGRES_USER and POSTGRES_DB; synthetic local connections use trust auth. Unix sockets are disabled. Native database time remains machine time. An occupied port or ownership mismatch is refused; teardown evidence is retained if shutdown cannot be verified. Other supported containerless kinds retain their existing implementation. Do not change the infrastructure implementation over a live instance or treat retained PGlite files as native PostgreSQL: stop and purge a disposable World before changing its infrastructure implementation.

For a browser or other client without the process injector, an issued URL can be requested as $issue:<service>@direct. It retains the twin-issued URL's user information, path and query and replaces its scheme and authority with that service's endpoint. Opaque tokens, non-HTTP URLs and credential files are refused in this form. For a URL embedded by a build, declare a stable endpoint port so the same URL serves after closing build permissions and resuming runtime state.

View Markdown source

On this page