---
title: "Policies"
summary: "CalVer release identity, minimum runner and plan-schema policy, the executable plan and approval contracts, and the v1 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 v1 contract guarantees across upgrades"
---
## Release identity

Onebox releases use `vYYYY.M.REVISION`, for example `v2026.8.0` for the first
release in August 2026. The year has four digits, months are unpadded, and each
UTC calendar month starts at revision zero.

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:
      minimum_onebox_version: v2026.8.0
      minimum_plan_schema: 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 — 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.

When approval policy is enabled, migrations and unknown data effects use the
**strong ceremony**, where the operator types the release ID.

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 |
| --- | --- | --- |
| `require_approval` | `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 canonical` · `ob doctor` · `ob eject` · `ob init` · `ob job plan` · `ob plan` · `ob preflight` · `ob preview` · `ob schema` · `ob secrets list` · `ob status` · `ob validate` · `ob version` |
| Finite operation stream | yes | yes | `ob abort` · `ob bootstrap` · `ob deploy` · `ob destroy` · `ob job run` · `ob proxy apply` · `ob resume` · `ob rollback` · `ob secrets push` · `ob service apply` |
| Operator passthrough | finite only | yes | `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
rather than the exit status.

| 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.

## Pre-release evolution

Onebox is not released. The current contract is intentionally strict and may
replace earlier development shapes without aliases, migrations, or hidden
fallbacks. Unknown fields and unsupported schema identities are rejected.
Compatibility guarantees begin with the first public release, not with local
development snapshots.

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.**

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.