# How it works

Every change travels the same steps. Nothing runs on a schedule against your
repositories; Upseam works from events. In the `auto` mode the approval step
is skipped, except for the kinds of pull requests that
[always ask](delivery-modes.md#always-ask).

```text
 vendor change
      │
 ┌────▼────┐
 │ detect  │ what changed
 └────┬────┘
 ┌────▼────┐
 │  match  │ which lines it touches
 └────┬────┘
 ┌────▼────┐
 │ approve │ ask mode: Slack buttons
 └────┬────┘ or the dashboard issue
 ┌────▼────┐
 │  patch  │ your model, then gates
 └────┬────┘
 ┌────▼────┐
 │   PR    │ your CI runs, you review
 └─────────┘
```

## Detect

Upseam starts work from four events:

- **A vendor publishes a change.** Once an hour Upseam reads the providers'
  changelogs and deprecation pages and records new change events. Each event
  carries its type, the official breaking flag, the symbols to search for and
  a link to the vendor's page.
- **You install the App or add a repository.** Upseam scans the default branch
  once.
- **A push to the default branch** changes a manifest or lockfile,
  `.github/upseam.yml` or a source file Upseam reads. Other pushes are ignored.
- **Dependabot or Renovate opens a pull request** that bumps the SDK of a
  tracked provider.

For each repository Upseam records its surface: the provider SDK, the SDK
version from the lockfile or manifest, and the API version from the pin in
code. Without a pin, the API version is inferred from the SDK version where
Upseam knows the default for that SDK release; otherwise it is unknown. Only
breaking vendor events reach repositories: for versioned APIs, repositories
whose recorded version is below the event's version; for AI model
retirements, every repository using that provider. Because the recorded
version may be inferred, a repository can be reached by a change that does
not apply to it, or missed by one that does.

## Match

Upseam downloads the repository at its current commit and searches it for the
symbols of the breaking changes newer than your version. Matching is textual
and word-bounded. It skips comments and lines that start with `import` or
`from`, and ignores `node_modules`, `dist`, `vendor` and similar directories.
Model ids match only as whole ids: a neighbouring letter, digit or hyphen
makes a different id, and ids made only of letters and digits, such as
`davinci`, match only inside quotes. Each match is a file and a line. No match, no action.

Matched breaking changes are grouped. One group becomes at most one pull
request:

| Change              | Group                                              |
| ------------------- | -------------------------------------------------- |
| Versioned API       | repository, provider, target API version           |
| AI model retirement | repository, provider, notice date                  |
| Internal contract   | consumer repository, backend repository, spec path |

A group can be fixed automatically when every change in it is mechanical (a
removal, a rename or a type change) and the target version works with the SDK
you have. If any change in the group is not, the whole group is listed under
**Needs you** with a reason, for example "Bump SDK" when the change needs a
newer SDK.

## Patch

The patch is written by **your** model, with your key
([Connect a model](models.md)). The model receives only the change data and
the files with matches, and answers with whole files. Before anything is
pushed, deterministic gates check the answer: only the matched statements may
change, only the changed symbol may be renamed, and no new imports, URLs,
network calls or environment access may appear. A patch that fails a gate is
not pushed; the finding goes to **Needs you**. The full list is in the
[Security model](security.md#patch-gates).

Until you connect a model or your own agent, Upseam writes no fixes; fixable
findings are listed as ready to fix instead. See
[Without a model](models.md#without-a-model).

## Pull request

Upseam pushes one commit to a branch `upseam/<group>` through the GitHub API,
so GitHub marks it Verified, and opens a pull request against the default
branch. The body has these sections:

- **What changed at** the vendor, for example "What changed at Stripe": the
  change, its date and a link to the vendor's page.
- **Changes**: the lines Upseam changed.
- **Not changed: needs you**: matches Upseam left alone, with file and line.
- **New in** the vendor's API version, for versioned APIs: additions between
  your version and the target version, listed from the vendor's changelog.
  Upseam does not use them in your code. The list is left out when **New
  capabilities** is off.
- **Verification**: which files and how many lines changed, and the result of
  your CI on the pull request.
- **How this was made**: the model that wrote the patch, with your key.

Your own CI runs on the pull request as on any other. Upseam reads the result
and shows it in the pull request, and in the Slack message when the pull
request came from an approval there.

- **No duplicates.** A group has one branch and at most one open pull
  request. A newer change in the same group updates the branch, but only
  while its head is the last commit Upseam pushed. If someone else committed
  to it, Upseam does not touch the branch and moves the finding to **Needs
  you**.
- **Closed means ignored.** A pull request closed without merging switches its
  group off. The next target version is a new group.
- **Limit.** At most five open Upseam pull requests per repository.

## The dashboard issue

When a repository has its first finding that waits for approval or needs you,
or a setup notice (for example a missing workflow for
[generation in your Actions](models.md#generate-in-your-github-actions)),
Upseam opens one issue, **Upseam watches this repository**, and keeps it up to
date:

- **Waiting for approval**: findings in the `ask` mode, and successor-model
  and Dependabot or Renovate findings in any mode, with a checkbox to open the
  pull request.
- **Needs you**: changes Upseam will not patch, with file and line.
- **Removed at or before your pin, still referenced in code**: removals from
  API versions at or before the pinned one that your code still reads. They are
  listed apart from the changes after the pin. Upseam always asks for approval
  for them, also in `auto` mode, and opens a pull request only after you
  approve; closing it works as for any other change.
- **Watching**: the providers, SDKs and versions Upseam found.
- **Setup**: shown when fixes are generated in your GitHub Actions and the
  `upseam-generate` workflow is missing, with what to add.

Snoozed findings leave **Waiting for approval** until the snooze ends. A
closed dashboard issue is not reopened. With [Slack](slack.md) connected,
findings waiting for approval also arrive in your channel with **Open PR**,
**Snooze 7 days** and **Ignore** buttons. Code review stays in GitHub.

## Successor models

When a vendor retires a model and names exactly one replacement for each
retired model your code uses, the pull request switches the model id to the
replacement. Its body has a "Needs your review" section, because the successor
may accept different parameters and behave differently. These pull requests
always wait for approval, even in the `auto` mode. When the vendor names
several replacements, the finding goes to **Needs you** with the choices.

The **New capabilities** switch on the settings page, per installation or per
repository, turns successor-model pull requests on or off. When it is off,
those retirements are listed under **Needs you** instead.

## Internal contracts

List an OpenAPI or GraphQL spec and its consumer repositories in the backend's
[`.github/upseam.yml`](configuration.md). When a push to the backend's default
branch changes the spec, Upseam compares the two versions, searches every
consumer for the changed operations, types and fields, and opens a pull
request in each affected consumer. Consumers must be in the same App
installation.
