> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nextmoca.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Versioning

> Major-only URL versions, what counts as breaking, and the commitment that a version keeps serving until it is formally deprecated.

Three different things get versioned here, and they move independently. Keeping
them apart is most of what this page is for.

| Axis                | Example          | Names                                                              |
| ------------------- | ---------------- | ------------------------------------------------------------------ |
| **URL version**     | `/v1`            | The wire contract's major revision                                 |
| **Operating point** | `np-2026-07-r2`  | A frozen engine configuration                                      |
| **Meter version**   | `npm-2026-08-r1` | The definition of a billable unit, echoed in every `usage` receipt |

Minting a new operating point does not imply a new URL version. A repricing does
not change a selection. A retune does not reprice anyone. Only a breaking change
to the wire contract mints `/v2`.

## URL versions are major-only

The only version identifiers that ever appear in a path are `/v1`, `/v2`, `/v3`.
A minor or patch number never appears in a URL.

* A **breaking change** is a major bump and gets a new URL version.
* **Patches and minor additions stay at the current URL version.** A new
  optional response field, a new optional request field with a safe default, a
  new operating-point label, a bug fix — all ship at the existing `/vN`.

### What counts as breaking

The test is the **client's**, not ours: a change is breaking if a correct client
written against the current contract stops working.

| Not breaking                                         | Breaking                                  |
| ---------------------------------------------------- | ----------------------------------------- |
| Adding an optional response field                    | Removing or renaming a field              |
| Adding an optional request field with a safe default | Making an optional field required         |
| Adding a new operating-point label                   | Narrowing an accepted range               |
| Adding a new `gate.reason` prefix                    | Changing a default                        |
| Fixing a bug                                         | Changing the meaning of an existing value |

## Nothing is served unversioned

The version segment is an explicit route. A path outside a known version prefix
matches nothing and is rejected before any of our code runs. There is no
unversioned route and **no implicit "latest"**.

Clients pin a URL version explicitly, exactly as they pin an operating point.

## Two rules that keep your client working

These are the client's side of the bargain. A client that ignores them will
break on a change this contract considers non-breaking — and that will be the
client's bug, not a broken promise.

<Steps>
  <Step title="Ignore response fields you do not recognise">
    New optional fields ship within `/v1`. Strict schema validation that rejects
    unknown fields will fail on the next additive change. The first one already
    designed is the `usage` block.
  </Step>

  <Step title="Do not exhaustively match the gate outcome or its reason">
    The gate outcome is an **open, extensible enum**. Today it takes two values —
    a *select* outcome (`gate.engaged: true`) and a *stand down* outcome
    (`gate.engaged: false`) — with the trigger carried in `reason`
    (`engage:needle`, `standdown:high_drift`, …).

    A third outcome, *escalate*, is planned: it signals that a caller declared a
    hard downstream capacity cap and neither a sufficient within-cap selection nor
    full pass-through was available. It will arrive **additively** — a new `reason`
    prefix and/or a new optional field — so a client that tolerates unrecognised
    prefixes will not break.
  </Step>
</Steps>

## A version keeps serving until it is formally deprecated

**Standing up `/v2` is never, by itself, grounds for switching off `/v1`.**

Formal deprecation requires all of:

1. **A published deprecation notice** naming the version, its replacement, and a
   sunset date.
2. **A migration path** — what a caller must change, in writing.
3. **A notice period that has actually elapsed** before removal, long enough
   that a pinned client is not broken by surprise.

Until every one of those is satisfied, the version keeps serving.

<Note>
  This commitment is load-bearing beyond ordinary API courtesy: published work
  cites an endpoint and an operating point, and those citations have to stay
  re-runnable. Retiring a URL version can invalidate a reproducibility promise
  attached to a published result, so it is not a decision that gets made on
  convenience.
</Note>

## Changes you should expect at `/v1`

Additively, within this URL version:

* **The `usage` block** on the response — the metering receipt. Additive and
  optional, which is why it does not mint `/v2`.
* **`mode: "shadow"`** as a third value for `budget.mode`.
* **An *escalate* gate outcome**, as described above.
* **New operating-point labels**, whenever the engine is retuned or a new engine
  revision is published.

None of these will break a client that follows the two rules above. That is why
the rules are stated before the changes.
