---
title: "Policies"
summary: "CalVer release identity, minimum runner and plan-schema policy, the executable plan and approval contracts, and contract evolution guarantees."
description: "Versioning, runner compatibility, plan and approval schemas, and the guarantees that hold across releases."
status: shipped
read_when:
  - "Pinning a minimum runner version for an environment"
  - "Understanding why a checkout build was refused"
  - "Checking what the project contract guarantees across upgrades"
---
## Release identity

Onebox releases use `vYYYY.M.REVISION-alpha`, for example `v2026.10.0-alpha` for the
first alpha release in October 2026. The year has four digits, months are
unpadded, and each UTC calendar month starts at revision zero. Every release
increments the revision.
An alpha precedes the stable version with the same year, month, and revision.

Checkout builds use Git-derived provenance and remain **visibly distinct** from a
release. `ob version` reports which you have.

## Runner and plan-schema policy

```yaml
environments:
  production:
    policy:
      minOneboxVersion: v2026.10.0-alpha
      minPlanSchema: onebox.run/executable-deploy-plan/v1alpha2
```

`ob doctor` reports whether the runner selected by `PATH` is compatible.

> **Commit-derived and dirty builds fail closed**
>
> When a minimum version is configured, they are refused because they are not released
> runners. An environment that pins a minimum is asking for a runner whose identity
> can be checked, and a dirty working tree has none.

## The executable plan

Executable plans use `onebox.run/executable-deploy-plan/v1alpha2` and include the
planner's version, source revision, build time, dirty state, and supported
schemas. Schema-less and unsupported plans are rejected.

A plan is a mode-`0600`, digest-protected envelope containing:

- the typed operation graph
- the exact config, Compose and host-state bindings
- image, rendered-Compose and payload bindings

**It expires after 15 minutes.** Any drift or local payload change requires a new
plan.

## The local confirmation

`ob approve` writes a mode-`0600`, digest-bound local confirmation covering the
plan, server, inputs, risk, operator label, backup-report digest, and expiry. A
changed or expired plan or report needs a new confirmation.

Migrations, destructive effects and unknown data effects use the **strong
ceremony**, where the operator types back what the plan acts on: the release ID
for a deploy, the job name for a job run. A deploy mints the release it names,
while a job runs inside the release already serving (which every job planned
against that release shares) so the job is what identifies it.

An operator job carrying one of those effects asks whether or not approval policy
is enabled. Policy decides only whether a deploy that is not critical asks at
all.

The artifact source is `local_cli`. Its digest detects modification and binds
the ceremony to reviewed inputs, but it is not an identity-provider signature
or proof that the actor requesting the change was technically unable to create
it. Independent authorization is not shipped.

## Approval policy fields

| Field | Default | Effect |
| --- | --- | --- |
| `requireApproval` | `true` | A plan-bound local confirmation is required before mutating |
| `allow_agent_proposals` | `true` | Recorded policy; the current CLI does not distinguish agent identity, and execution stays approval-gated regardless |
| `require_migration_backup` | `false` | Plan-bound backup report before migration risk |
| `migration_backup_maximum_age` | — | Maximum age of the accepted report |
| `require_migration_restore_test` | `false` | Report must state that a restore test succeeded |
| `migration_backup_key_material` | — | Key material whose usability the report must cover |

## Structured output contracts

Every machine-facing command uses one envelope:
`{schema_version, command, outcome, data|error}`. Outcomes are `success`,
`no_op`, `cancelled`, or `error`. Errors carry a stable `code`, a
`safe_message`, plus guidance in its exact semantic role: `diagnostic_command`
only inspects, `next_command` advances a workflow, and `resolving_command` can
actually remedy the condition. Diagnostics stay on stderr.

NDJSON uses the same schema identity. Every record adds a monotonic `sequence`
and a `kind`; every stream ends in exactly one `terminal` record. Passthrough
`stdout` and `stderr` chunks are tagged with their channel. Logs and exec are
operator-controlled bytes and may contain secrets; they are not described as
redacted.

### Classified leaf commands

| Class | JSON | NDJSON | Commands |
| --- | --- | --- | --- |
| Finite envelope | yes | no | `ob approve` · `ob audit` · `ob backup status` · `ob canonical` · `ob doctor` · `ob eject` · `ob init` · `ob job history` · `ob job plan` · `ob plan` · `ob preflight` · `ob preview` · `ob schedule list` · `ob schema` · `ob secrets list` · `ob status` · `ob validate` · `ob version` |
| Finite operation stream | yes | yes | `ob abort` · `ob backup create` · `ob backup enable` · `ob backup disable` · `ob backup drill` · `ob backup prune` · `ob backup restore` · `ob backup verify` · `ob bootstrap` · `ob deploy` · `ob destroy` · `ob job run` · `ob proxy apply` · `ob resume` · `ob rollback` · `ob schedule apply` · `ob schedule pause` · `ob schedule resume` · `ob secrets push` · `ob service apply` |
| Operator passthrough | finite only | yes | `ob job logs` · `ob logs` |
| Operator passthrough | no | yes | `ob exec` |
| Trusted editor | yes, after exit | no | `ob secrets edit` |

Anything not listed is native human output only. Finite commands accept JSON.
Operations accept JSON and NDJSON. `ob logs` accepts JSON when finite and
NDJSON when following; `ob exec` accepts NDJSON. `ob secrets edit` emits its
terminal JSON envelope after the editor closes. Help and completion remain
native Cobra output.

`ob exec` additionally requires a bounded single-line `--reason`. Audit stores
that reason, target, target kind, operator, outcome, and command digest; it never
stores command bytes or passthrough output.

### Exit codes

Three codes, and the set is closed. Every failure is `1`: there is no second
failure code, because which failure it was is the error `code` in the envelope. The exit status does not distinguish failures.

| Code | Means |
| --- | --- |
| `0` | Succeeded, or there was nothing to do. Both the `success` and `no_op` outcomes exit `0` |
| `1` | Failed |
| `2` | Cancelled: an interrupt, or an operator declining a confirmation or typing one back wrongly. Nothing was changed |

The codes hold whether output is human, JSON or NDJSON, and match the terminal
outcome in the envelope.

> **`ob exec` does not propagate the remote exit status**
>
> A container command that exits `3` is an exec that failed, and the CLI exits
> `1`. Only cancellation exits `2`. An agent that needs the command's own status
> must have the command report it in its output.

### Schema identities

An agent branches on the schema version and command, never on an inferred shape.

| Document | Schema identity |
| --- | --- |
| CLI envelope and NDJSON record | `onebox.run/cli/v1alpha1` |
| Executable deploy plan | `onebox.run/executable-deploy-plan/v1alpha2` |
| Executable job plan | `onebox.run/executable-job-plan/v1alpha1` |
| Job result protocol | `onebox.run/job-result/v1alpha1` |
| Local confirmation | `onebox.run/local-confirmation/v1alpha1` |
| Migration backup report | `onebox.run/backup-report/v1alpha1` |

> **The command matrix is under test; this table mostly is not**
>
> `cmd/ob/docs_test.go` asserts set equality in both directions for the
> [classified leaf commands](#classified-leaf-commands): every command the binary
> gives structured output appears in that list, and every command listed there
> really has it. Adding one without documenting it fails the build, and so does
> documenting one that does not exist.
>
> The identities above are not held to that standard. Only
> `onebox.run/cli/v1alpha1` is asserted, and only in one direction: that the page
> mentions it somewhere. The other five are maintained by hand and could drift
> without failing the build.

## Alpha releases

Onebox is in alpha. The contract may change as the product develops. Unknown
fields and unsupported schema identities are rejected. Use the documentation
and schema that match your installed release.

The JSON Schema published by `ob schema` is generated from the same declarations
the loader enforces and is checked against the conformance corpus, so what your
editor tells you while you type is what `ob validate` tells you afterwards.

## Safety claims are bounded

Application rollback, data recovery, and reversal of an external side effect are
different operations with different guarantees. Onebox classifies risk and
refuses when evidence or a driver contract is insufficient. **A force flag cannot
turn an unsupported operation into a safe one.** Override flags permit a specific
attempt; they do not change what the engine can prove. When evidence is
insufficient, Onebox stops and reports the available recovery choices.

A customer with root can still bypass Onebox, and a compromised host can lie
about its own evidence. Onebox does not claim universal reversibility, high
availability, or protection from an adversarial infrastructure provider.