# Contract Checks

A renamed field in an API spec breaks every client that reads it; contract checks catch those breaks before release, for teams that publish APIs or schemas.

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `contract_checks`.

Contract checks compare the API contracts a release changes, old against new, and report every change that breaks a client. They cover OpenAPI and Swagger specs, GraphQL schemas, protobuf files and JSON Schema payloads. Each finding gives the file and line on both sides, so you can see exactly what was and what is.

Contract checks run as part of every [release check](/docs/release-checks) once the flag is on. There is nothing to configure.

## Which files are checked

| Format | Detected when |
|---|---|
| protobuf | The file ends in `.proto` |
| GraphQL | The file ends in `.graphql`, `.graphqls` or `.gql` |
| OpenAPI / Swagger | A `.yaml`, `.yml` or `.json` file with a top-level `openapi` or `swagger` key |
| JSON Schema | A `.yaml`, `.yml` or `.json` file whose name ends in `.schema.json`, or whose `$schema` names a JSON Schema |

Files under `testdata/`, `fixtures/`, `__fixtures__/`, `examples/`, `example/` or `samples/`, and generated files, are skipped. Both versions of a file must be the same format to be compared.

The CLI (`bumpkit check`, `bumpkit gate`) sends the old and new text of each changed contract with the check, up to 20 files of at most 1 MB each. The GitHub App fetches them from the repository instead. Each file reports at most 50 changes, breaking ones first.

A contract file that is deleted is a break ("Removed the OpenAPI contract …"), unless a file with the same name and format was added elsewhere in the same release; then the two are compared as a move.

## Findings

| Kind | Severity | Meaning |
|---|---|---|
| `contract_breaking` | high | The change breaks existing clients |
| `contract` | info | A compatible change, such as a new operation, field or enum value, or a deprecation |

Findings come from the `contracts` checker, with `deterministic` evidence and `high` confidence. A `contract_breaking` finding makes the release a major version bump. The detail reads "Was: … (file:line). Now: … (file:line)."

## What counts as breaking

### OpenAPI

- Removing an operation, or a parameter
- Making a parameter or the request body required, adding a required parameter, or requiring a request body where there was none
- Changing the security scheme or the authentication an operation requires
- No longer accepting a request media type, or no longer returning a response media type
- Removing a success response
- Changing a property's type or format in a way the other side can't handle (narrowing what a request accepts, or widening what a response returns)
- Closing an object to properties it doesn't list (`additionalProperties`)
- Restricting a value to a fixed set (`enum`), or removing an enum value that requests use
- Removing a response property, making a property required, or making a response property no longer always present
- Adding a required property to a request

### GraphQL

- Removing a type, field, argument, enum value or union member
- Changing a type's kind, such as an object to an interface
- Changing a field's type, or an argument's type, unsafely
- Adding a required argument or required input field
- A type no longer implementing an interface

### protobuf

- Changing the package
- Removing a message, enum, service or RPC
- Changing an RPC's request or response type or streaming
- Removing a field or enum value without reserving its number
- Renaming a field (the binary encoding is unchanged, but JSON clients and generated code break)
- Changing a field's number, type or cardinality, or moving it into or out of a `oneof`
- Reusing a reserved number or name, or no longer reserving one

Removing a field and reserving it is reported as a compatible change.

### JSON Schema

A JSON Schema payload is compared in both directions, as data that is both sent and received. Type and format changes, closing an object, restricting or removing enum values, removing properties and making properties required are all breaking.

## Use them in the gate

`contract_breaking` findings work with the usual policy keys:

```yaml
gate:
  block_on: [secret, data_loss, contract_breaking]
  require_approval:
    - contract_breaking
  hotfix:
    block_on: [breaking, contract_breaking, data_loss, migration]
```

- [`block_on`](/docs/bumpkit-yml#block-on) accepts `contract_breaking` to fail on any high-severity break.
- [`require_approval`](/docs/bumpkit-yml#require-approval) accepts a `contract_breaking` rule. The `breaking` rule also asks for approval on contract breaks.
- [`hotfix.block_on`](/docs/bumpkit-yml#hotfix) accepts `contract_breaking`, and includes it by default.

Without a policy entry, a contract break gives the check a `review` verdict and needs approval under the default `breaking` rule.

## Compatibility matrix

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `compatibility_matrix`.

The compatibility matrix is an explicit table of the version pairs you support: client vX with server vY, SDK with API, schema with app. Each pair gets one verdict, read from your recorded release checks:

| Verdict | What it means |
|---|---|
| `compatible` | Every recorded release of the provider inside the range was checked in full, and none has a proven break. |
| `breaking` | A recorded release inside the range has a proven break. The pair names it, with its file and version. Later versions in the range carry it. |
| `not_checked` | The evidence isn't complete, and the pair says why. Not checked never means compatible. |

There are no AI verdicts and no percentages. Bumpkit doesn't run your client against your server: a verdict states what the provider's recorded checks prove.

### Set it up

1. Get access to `compatibility_matrix`, `contract_checks` and `version_consistency`. A check run without contract checks or version consistency counts as not checked.
2. Declare your components and the ranges you support in `bumpkit.yml`:

```yaml
compatibility:
  components:
    - name: web
      role: client
      repository: acme/web
    - name: api
      role: server
      paths: ["openapi/**", "proto/**"]
    - name: db
      role: schema
      paths: ["db/migrations/**"]
  supported:
    - consumer: web
      consumer_versions: ">=1.4.0 <2.0.0"
      provider: api
      provider_versions: ">=2.0.0"
    - consumer: api
      consumer_versions: ">=2.0.0"
      provider: db
      provider_versions: ">=2.0.0"
```

3. Run the gate, which reads the section every time, or read it now:

```bash
bumpkit compatibility sync --repository acme/api
```

4. Open **Release decisions**, or run `bumpkit compatibility`. Each row leads with a status lamp. Open a row to see the pair version by version. See [`compatibility` in bumpkit.yml](/docs/bumpkit-yml#compatibility) for every key.

### Where a verdict comes from

Bumpkit looks at the provider's repository and the recorded releases inside `provider_versions`. A version counts as released once Bumpkit published it or a check ran with that version's tag as its head. A check counts when both its previous version and its new version are inside the range.

| Provider role | What counts as a break |
|---|---|
| `client`, `server`, `sdk`, `api`, `app` | `contract_breaking` findings from these contract checks, and `breaking` findings such as a removed export or route |
| `schema` | `data_loss` findings from migration safety checks, such as a dropped table or column |

A break counts when it is high severity, deterministic, not marked as a false positive, and inside the component's `paths`. With no `paths`, the whole repository counts.

A pair is not checked when a release in the range has no recorded check, the diff was too large to check in full, contract checks or version consistency weren't on, a contract file couldn't be read on both sides, version consistency disputes the version number, or data retention deleted the check's findings. Checks recorded before you turned on `compatibility_matrix` don't count either.

### Block a release that breaks a pair

The rule is opt-in. Without it, the gate only reports the break:

```yaml
gate:
  block_on: [secret, data_loss, compatibility_break]
```

The gate reads the pairs in the bumpkit.yml of the repository it runs on, and applies each pair whose provider is that repository. A release breaks a pair when its previous and new versions are both inside `provider_versions`. A proven break makes a release a major version, so a closed range such as `>=2.0.0 <3.0.0` doesn't cover the release that leaves it: `3.0.0` isn't a version you declared as supported. To block that release, leave the range open, such as `>=2.0.0`.

A pair the gate can't prove adds a `compatibility_not_checked` finding, which never blocks.

### Export it for a procurement review

Use **Export CSV** or **Export JSON** on the Release decisions page, or the CLI:

```bash
bumpkit compatibility export --format csv > compatibility.csv
```

The CSV has one row for each pair, with `provider_version` set to `all in range`, then one row per version and per break, with the file, line and check it came from.

## Internal contract alerts

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `contract_alerts`.

Contract checks cover the contracts you publish. For code shared between branches, `bumpkit contracts` alerts other open branches when a Go function, method or type they call changes signature:

```bash
bumpkit contracts sync
bumpkit contracts alerts --branch feature/export
bumpkit contracts ack ALERT_ID
bumpkit contracts close --branch feature/export
```

Run `sync` on every push, in CI or from an agent. It records this push's Go signature changes and the symbols the branch calls; branches calling an old signature are alerted by pull request comment, webhook and MCP. See [Agent Swarms](/docs/agent-swarms).
