Environments
Create, update, test, and remove branch-bound preview environments without confusing them with production.
A business has production plus zero or more preview environments. A preview is an active platform target bound to one Git branch. Its compiled contract, runtime hostname, docs snapshot, frontend release, variables, backends, test personas, usage, and customers are scoped by environment where the corresponding surface supports it.
Those are per-environment operational records. The environment branch is the sole source of that environment's Business SDK contract. Plans, pricing, routes, permissions, meters, limits, policies, and frontend declarations are compiled from that branch and are never inherited from production. A plan that exists only on the default branch does not exist in the preview. Until the environment has accepted its first branch contract, contract-backed operations fail closed instead of borrowing the production snapshot.
Backend targets are the only fallback because origin URLs are operating state, not Business SDK declarations. A preview resolves a declared backend's stable slug to the production binding until a concrete environment override is bound. This reuses where requests run; it does not copy any production contract state.
Routes retain that stable slug across environments. The gateway resolves it to the inherited production backend or the concrete preview override at request time; physical backend IDs and credentials never leak across environments.
The Business program remains repository-owned in every environment. Environment rows, hostnames, bindings, credentials, and runtime state are platform-owned.
Choose how previews are created
farthershore business preview-env set acme \
--trigger branch-prefix \
--branch-prefix env/ \
--format json
farthershore business preview-env set acme --trigger pull-request --format json
farthershore business preview-env set acme --trigger disabled --format json
branch-prefixis the default. A push whose branch starts with the prefix creates the environment named by the remaining suffix.env/paymentsbecomespayments; pushing the bareenv/prefix creates nothing.pull-requestcreates previews only for same-repository pull requests into the default branch. Fork PRs and PRs into another base are skipped. Closing the PR tears its preview down.disabledstops automatic creation.
In every mode, pushes to a branch that already has an environment row rebuild that environment. Deleting that tracked branch triggers teardown. Explicit CLI creation also remains available in every mode.
What a rebuild does to the environment's subscribers
An environment exists to match its branch exactly, so a push that changes the contract rebuilds the environment from scratch: its plans and compiled plans are replaced, and its subscriptions are deleted along with the API keys they own. Test personas survive as users, but the key each one held does not. Afterwards:
farthershore persona list <business> --env <name>reports the affected personas withrevokedAtset andrevokedReason: "env_reset".- A request that still presents one of those keys is denied
401withcode: "credential_env_reset"— distinct fromcredential_revoked, because nothing was revoked for cause and the product is healthy. Retrying the same key cannot succeed. - Recover by subscribing again, or, for a test persona, by running
farthershore persona bootstrapwith a fresh idempotency key.
A push that does not change the contract is a no-op for all of this. If the
commit compiles to the contract the environment has already accepted and
published — a comment, a README, a frontend-only change, or a re-push to retry
CI — the apply reports No contract change and
ENVIRONMENT_CONTRACT_UNCHANGED, nothing is rebuilt, and every subscription,
key and persona keeps working. A frontend-only commit still triggers its
frontend build.
A push whose build or compile fails performs no destructive work at all: the environment keeps serving its last published release, keys included.
Create explicitly
farthershore env create acme \
--name payments \
--branch env/payments \
--format json
Environment creation is convergent, not a replayed idempotency operation. On a
timeout or interrupted response, run farthershore env list acme --format json
and then repeat the same name-and-branch request if needed; the platform reads
current state and repairs the managed branch before exposing the environment.
Explicit creation accepts only the business's reserved preview namespace
(normally env/) and durably claims that exact branch before GitHub creates or
resolves it. A newly created environment reports branchCreated; a converged
existing environment omits it. A successful response always has an exact
managed branch head, and deleting that explicitly managed environment removes
its claimed branch. Webhook and PR environments retain their external source
branch ownership. If GitHub cannot create or resolve the branch, creation fails
safely: restore GitHub access and repeat the same request.
git fetch origin env/payments
git switch --track origin/env/payments
Every push reflashes the environment
A push to a tracked environment branch does not patch the environment — it wipes and recompiles it. Test personas, subscriber API keys, and customer identity rows for that environment are destroyed and the contract is rebuilt from the new commit.
Re-mint the persona after every push, and never carry a credential across one:
the API key that worked before the push is gone, not merely stale. Wait for the
apply to report applied before minting, or the mint races the recompile and
fails with TEST_PERSONA_ENVIRONMENT_NOT_READY.
If the branch already exists and the business has an accepted contract, explicit creation may queue its initial environment build immediately.
Find the environment and test it
farthershore env list acme --format json
farthershore apply-timeline list acme --env payments --format json
farthershore business show acme --env payments --format json
env list returns active preview rows with their exact id, name, branch,
runtimeHostname, portalHostname, and status. Use those returned values;
do not construct hostnames from the environment name.
business show --env <name> returns the business-level accepted spec — the
contract compiled from the default branch — not the environment's own accepted or
edge state. To see whether an environment has accepted its branch contract, read
its Apply Timeline entry; that is the authoritative per-environment view.
Wait until the branch's Apply Timeline entry reports applied and both
Accept contract and Publish to edge report passed. A successful preview
publish promotes its matching commercial release before subscriber credentials
are issued or repinned; do not mint a persona against a still-running apply.
For an API business, issue a persona only in a test environment and exercise the preview runtime:
farthershore persona bootstrap acme --env payments --plan pro --format json --idempotency-key <persisted-persona-bootstrap-attempt-key>
farthershore persona list acme --env payments --format json
For a customized frontend, inspect the environment's frontend state:
farthershore frontend status acme --env <environmentId> \
--ref "$(git rev-parse HEAD)" --wait --format json
frontend status and frontend rollback accept an environment name or id.
Run the status command from the checkout at the commit pushed to that preview
branch.
The CLI resolves a name to the current environment id before it calls Core.
Not every command accepts --env; run the exact command's --help rather than
assuming a global environment flag exists.
Runtime tokens are scoped to backend rows, so create the environment's backend
binding before minting its token, and pass --backend <backend-id> when the
environment has more than one backend. backend list and backend tokens list
are business-wide and accept no --env.
If a preview request returns origin_unavailable, the environment has no usable
upstream for that route. Inspect both the logical slug and its effective binding:
the preview uses its concrete override when present, otherwise the production
backend. It never falls through to a different preview. An environment-only
backend with no target still fails safely; do not weaken request verification.
Production is different
The repository default branch targets production. An ordinary push validates the contract, and a provably non-economic contract change may publish its runtime state immediately. Economic or indeterminate contract changes wait for a published GitHub Release. Production frontend and repository-docs artifacts also build from the release, not from a plain default-branch push.
Production is not returned as a deletable BusinessEnvironment row, and env delete is never a production rollback.
Delete a preview
farthershore env delete acme payments --yes --format json
Deletion tries to remove edge state, any Git branch Core claimed for the
environment, environment docs, in-flight workflows, personas and customer
identity rows, compiled plans, environment-only permission inventory, backend
credentials, usage-meter state, and the Core environment row. It is deliberately
retryable.
ENVIRONMENT_CLEANUP_FAILED means some cleanup did not complete; the
environment row remains so you can run the same deletion again.
A live-provider environment with active subscriptions cannot be torn down. Move or end those subscriptions first. Test environments do not have that live subscription guard.
Deleting the environment is destructive operational work. It does not delete the Business program from repository history.
See Inspect an apply, Production releases, Test in a preview, and Infrastructure with OpenTofu for provisioning a separate backend deployment per environment.