---
title: "Versions"
url: https://services.dev/docs/operate/versions
captured: [operate-versions.txt]
---
# Versions

URL: https://services.dev/docs/operate/versions

> Each deploy is a version, the whole bundle under one sha256. A job keeps the version it was ordered under until it ends, a key keeps the contract of its first call, and check --against live sorts every change into additive or breaking. You move jobs in flight to a new version with a dry run first, and a rollback changes only what new orders take.

A version answers one question: which rules is this job running under? A version is the whole bundle, the Service files and every module they import, the rules, the Pools and the brand, named `2026-11-02.1` (or by `--name`) and fixed by one sha256. Nothing in a version changes after it is built.

## What a version pins

| What | Keeps |
|---|---|
| a job | the version it was ordered under, from intake to its end, with that version's rules, price and plan |
| a job minted by a subscription | the version live at the mint |
| a job's line | the rung its version named, so a line that moves reaches new jobs only: [Move a step to AI](https://services.dev/docs/operate/line.md#what-buyers-see-when-a-line-moves) |
| a key | the contract of its first call: the shapes, fields and errors it was built against. `Services-Version: <version>` overrides it for one request |
| a clock on a subject | its job's version, until a newer job of the Service joins the subject: [Timers and replay](https://services.dev/docs/service/timers.md#across-versions) |
| a version anything is pinned to | stays runnable, and retires when its last job, clock and timer close |

So a deploy never changes a job that is already running. The new version reaches new orders, and the jobs you choose to move.

## What counts as breaking

`check --against live` sorts every difference between your folder and live:

| Additive: ships with no flag | Breaking: refused with `breaking_change` |
|---|---|
| a new Service, step, lens, ask or role | a Service removed, or its id changed |
| an optional input field, a new output field | an input made required, or an output field removed or retyped |
| a new event, operation or error code | a step renamed or removed while pinned jobs still have it ahead |
| a price change for new orders | an operation or event removed, or its data narrowed |

A breaking change ships three ways: under a new Service id, beside the old one; after the jobs that need the old shape end; or with `deploy --live --breaking`, which keeps every pinned job and key on its own version. Each key's version is on the owner console, so you see who still calls the old contract before you retire it.

## Moving jobs in flight

api.lawyer adds a fourth lens to its trademark response's adversarial review. It is additive:

```bash
npx services.dev check --against live
```

```txt title="magic values: none"
api.lawyer · against live 2026-10-19.3 · sha256:9c02d1aa7e40b3c8…
  additive  services/trademark-response.ts:19  attack.lenses + 'genericness'
  breaking  none
  pinned    41 matters on 2026-10-19.3 · 38 trademark responses, 3 contract reviews
```

Thirty-eight responses are still on the old version. A repin dry run sorts each one before anything moves:

```bash
npx services.dev jobs repin --from 2026-10-19.3 --to 2026-11-02.1 --dry-run
```

```txt title="magic values: none"
41 matters on 2026-10-19.3 · dry run, nothing changes
  repin     29 trademark responses · each takes 2026-11-02.1 at its next step, so attack runs with 4 lenses
  unchanged 6 trademark responses · past attack: nothing ahead of them differs · they stay on 2026-10-19.3
  waits     3 trademark responses · a review claimed under 2026-10-19.3 · each repins after the signer's act
  never     3 contract reviews · nothing in the diff touches them
  run it    npx services.dev jobs repin --from 2026-10-19.3 --to 2026-11-02.1
```

The rules a repin keeps:

- It takes effect at the job's next step, never inside one. A committed step never runs again.
- A step claimed by a person finishes on the terms it was claimed under, and the job repins after their act.
- A job whose committed steps aren't all in the new graph can't repin, and the dry run says why.
- Price, plan and rules stay as ordered. A repin changes how the rest of the work runs, never what the buyer agreed to pay.

```bash
npx services.dev jobs repin --from 2026-10-19.3 --to 2026-11-02.1
```

```txt title="magic values: none"
✓ 29 matters repinned now · 3 when their reviews end · job.repinned on each: from, to, and the step it takes effect at
```

## Rolling back

A rollback sends new orders to an earlier version, and it is checked the way a deploy is. `rollback` runs `check --against live` in reverse, so a key first called on the newer version keeps that version's contract, and every job keeps the version it runs on. A rollback that removes a field, step or operation a pinned key or job relies on is refused with `breaking_change` unless you pass `--breaking`. Rolling back to a retired version first re-runs its suite in test mode, from the bundle its sha256 kept.

```bash
npx services.dev rollback 2026-10-19.3 --dry-run
```

```txt title="magic values: none"
api.lawyer · live 2026-11-02.1 → 2026-10-19.3, for new orders · dry run, nothing changes
  reverse   check --against 2026-11-02.1: 1 removed lens, attack.lenses − 'genericness' · 0 breaking
  keys      4 keys first called on 2026-11-02.1 keep its shapes · Services-Version tells each one apart
  jobs      32 matters on 2026-11-02.1 keep it to their ends
  run it    npx services.dev rollback 2026-10-19.3
```

```bash
npx services.dev rollback 2026-10-19.3
```

```txt title="magic values: none"
api.lawyer · live 2026-11-02.1 → 2026-10-19.3, for new orders
  stays     every matter keeps the version it runs on · jobs repin --to 2026-10-19.3 moves them back
  timers    arms nothing, rewrites nothing
  event     version.rolled_back · 2026-11-02.1 stays runnable while anything is pinned to it
```

To move jobs back too, repin them `--to` the earlier version, with its dry run first. Every version still pinned:

```bash
npx services.dev versions
```

```txt title="magic values: none"
api.lawyer · 3 versions
  2026-11-02.1  live     sha256:4e1b77c0  since 2 Nov 15:12 · 32 matters repinned to it
  2026-10-19.3  pinned   sha256:9c02d1aa  9 matters · 18 clocks · 63 timers · retires when they close
  2026-10-19.2  retired  sha256:51f0b6e2  nothing pinned since 30 Oct
```

The tax brand's June change, where a job keeps its rungs across a deploy, is the [timer-level view](https://services.dev/docs/service/timers.md#across-versions).

## Controls

| Call (CLI) | API | Event | On the console | Plan |
|---|---|---|---|---|
| `check --against live` | `POST https://services.dev/v1/deploys { dry_run: true }` | — | `/console/deploys` | every plan |
| `jobs repin --from <version> --to <version> [--dry-run]` | `POST /owner/versions/:from/repin { to, dry_run }` | `job.repinned` | `/console/deploys` | every plan |
| `jobs repin <job> --to <version>` | `POST /owner/jobs/:id/repin { to }` | `job.repinned` | `/console/jobs` | every plan |
| `rollback <version> [--dry-run] [--breaking]` | `POST https://services.dev/v1/deploys/:version/live { rollback: true, dry_run }` | `version.rolled_back` | `/console/deploys` | every plan |
| `versions` | `GET /versions?pinned=true` | none: it reads | `/console/deploys` | every plan |

---

- Up: [Run your Service](https://services.dev/docs/operate.md)
- Previous: [Test and live](https://services.dev/docs/operate/testing.md)
- Next: [Observe](https://services.dev/docs/operate/observe.md)
