# Breaking changes: `wai breaking`

`wai breaking --against <git-ref>` compares a WAI checkout's working tree with a git ref and
reports every change that breaks something written against the ref: a reader of WAI's bytes, a
verifier of its signed records, or a program built on its libraries. WAI's repository runs it on
every pull request that touches WAI.

```bash
wai breaking --against origin/main            # every category
wai breaking --against v0.4.0 --category wire # the bytes a reader must accept, alone
wai breaking --against origin/main --json     # the findings as JSON, for a program or an agent
```

It compares against the merge base of the ref and `HEAD`, so what the ref gained since a branch
left it is not read as the branch removing it.

## What it checks

The categories are cumulative, strictest by default: `--category api` (the default) reports all
three, `record` reports wire and record, and `wire` reports wire alone. `--only` and `--skip`
choose surfaces by name.

| Category | Surface | What breaks |
|---|---|---|
| wire | `registry` | In the registry export (`registry/capabilities.json`): a capability, deterministic identifier or wire identifier (a magic, document kind, header, path, scheme or tag) that is gone; a capability whose kind, media, role, tier, licence, replicate class, edition or operator set changed, that left a set or joined the mandatory floor, or that was demoted to candidate; a wire identifier whose status fell back |
| wire | `corpora` | A vector of a media or delivery corpus that is gone or whose bytes changed; in a JSON file, an entry or member that is gone or a value that changed. An added entry is compatible |
| record | `domains` | A domain-separation prefix, magic or version a signed record is built from (a constant named `…DOMAIN…`, `…MAGIC…` or `…VERSION…` in the WAI crates' sources) that changed or is gone |
| record | `corpora` | A vector of a record corpus (claims, receipts, keys, live records) that is gone or changed: a record's fields, its signing preimage or its verdict |
| record | `schemas` | A published JSON Schema that refuses a document it accepted: a property gone or newly required, a type or enumeration narrowed |
| api | `rust` | The public API of the `wai` and `wai-quantum` crates, read from the compiler's own description of it (rustdoc's JSON output): a public path that is gone, a signature that changed, a struct that can no longer be built with a literal, an enum or struct made non-exhaustive, a field added to a struct a literal builds, a variant added to an exhaustive enum, a required method added to a trait |
| api | `c` | The C header `wai-rs/include/wai.h`: a function that is gone or whose return or parameter types changed (parameter names are not compared), a struct whose fields changed, an enumerator or constant that is gone or holds another value |
| api | `openapi` | The room service's OpenAPI document: a route that is gone, an `operationId` that changed, a parameter gone or newly required, a request body newly required, a success response or one of its properties gone |

The corpora hold the bytes the reference code writes and signs, and WAI's conformance workflow
requires every corpus to regenerate byte for byte from that code. So a change to what the code
writes or signs, such as a field or a signing preimage, reaches a corpus file, where `wai
breaking` reads it.

## Each finding

A finding has an id, `<category>:<surface>:<item>`, and says what changed and why it breaks:

```text
  BREAK api:c:wai_envelope_detect_version
        changed (api · c): the function wai_envelope_detect_version:
          int wai_envelope_detect_version(const uint8_t *, uintptr_t)
          → int wai_envelope_detect_version(const uint8_t *, uint32_t)
        why it breaks: a caller compiled against the old prototype passes or reads the
          wrong types: undefined behaviour, or a build that fails
```

Additions are listed as compatible.

## An intended break

A break passes only when the changelog names it. Add a line to `wai/CHANGELOG.md`, under the
current version, in an entry that says **Breaking**, with the finding's id in backticks:

```markdown
- **Breaking:** `api:c:wai_envelope_detect_version`: the length is a 32-bit count; callers pass `uint32_t`.
```

Only lines the change itself adds count: an entry already in the changelog acknowledges nothing
new. For each break it finds, the run prints the line to add.

## Exit status and JSON

| Exit | Meaning |
|---|---|
| 0 | No unacknowledged breaking change |
| 1 | A breaking change the changelog does not name |
| 2 | A usage error |
| 5 | The ref, git or the Rust toolchain is not found |
| 7 | An internal error, such as a documentation build that fails |

With `--json` it prints one JSON envelope, the report as its `data` (without `--json` the output
is text, on a terminal or not):

```json
{ "wai": "envelope/1", "op": "compat.check", "ok": false,
  "data": {
    "against": "origin/main", "base": "<commit>", "category": "api",
    "breaking": 1, "acknowledged": 0, "compatible": 0,
    "findings": [{ "id": "api:c:wai_envelope_detect_version", "category": "api", "surface": "c",
                   "item": "wai_envelope_detect_version", "change": "changed", "what": "…",
                   "why": "…", "breaking": true, "acknowledged": false }],
    "notes": [] } }
```

The API category documents each crate twice, the base and the working tree, with the toolchain
the repository pins, and only when a source it builds from changed. `--target-dir <dir>` keeps
that build's files between runs; `--skip rust` leaves the Rust API out where no Rust toolchain is
installed.
