# Yank and deprecate

Two operations that sound similar and are not.

| | Effect on resolution | Reversible | Deletes anything |
|---|---|---|---|
| **Yank** | stops being chosen | yes | no |
| **Deprecate** | none | yes | no |
| **Unpublish** | version is gone | no | yes |

**Yank** a version nobody should get by accident: a bad build, a wrong commit,
a release with a secret in it. **Deprecate** a version you would still rather
people did not pick, but which works.

Neither deletes anything. `unpublish`, with its 72-hour window, remains the
only destructive operation.

## Yank

A yanked version:

- is **not deleted**, and unyanking restores it;
- **stays downloadable by exact version**, so lockfiles that already pin it
  keep resolving and existing builds do not break;
- is **flagged** wherever the protocol has a place for the flag, and **excluded**
  where it does not;
- **never answers as latest**.

```sh
packr-cli yank   cargo/serde@1.0.0 --reason "built from the wrong commit"
packr-cli unyank cargo/serde@1.0.0
```

Ecosystem-agnostic API:

```
POST /api/v1/admin/packages/yank
{"ecosystem":"cargo","name":"serde","version":"1.0.0","yanked":true,"reason":"…"}
```

Cargo also has native verbs, which the registry implements:
`DELETE /cargo/api/v1/crates/{crate}/{version}/yank` and `PUT .../unyank`.

### How each ecosystem shows it

| Ecosystem | Representation |
|---|---|
| cargo | `yanked: true` in the sparse index |
| PyPI | PEP 592 `data-yanked` / `"yanked"` in the simple index |
| JVM | `yanked` on the index line |
| NuGet | `listed: false` — the protocol's own word for it |
| Go | absent from `@v/list`; `.info`, `.mod` and `.zip` keep serving so `go.sum` still verifies |
| Maven | absent from `<versions>` in `maven-metadata.xml`; the jar and pom keep serving |
| npm | excluded from `latest` resolution |

Go and Maven have no in-protocol yank concept, so absence from the listing *is*
the representation. That is why the exact-pin routes deliberately keep working.

## Deprecate

A deprecated version still resolves, still answers as latest if it is newest,
and still installs. The only effect is that clients whose protocol has
somewhere to put the message show it.

```sh
packr-cli deprecate   pypi/widget@1.0.0 --message "use widget2"
packr-cli undeprecate pypi/widget@1.0.0
```

```
POST /api/v1/admin/packages/deprecate
{"ecosystem":"pypi","name":"widget","version":"1.0.0","message":"use widget2"}
```

An empty `message` clears the notice, matching `npm deprecate ""`. A *missing*
`message` field is a 400 rather than a clear, so a malformed request cannot
silently undo a publisher's warning.

npm's own `PUT /-/v1/deprecate` still works, but only reaches npm packages.

### Where it is rendered

| Ecosystem | Representation |
|---|---|
| npm | `deprecated` on the version, in the packument and the version manifest |
| NuGet | `catalogEntry.deprecation`, the protocol's own model |
| JVM | `deprecated` on the index line |
| cargo, PyPI, Maven, Go | **not rendered** |

The last row is deliberate. The cargo sparse index, PEP 503/691 and
`maven-metadata.xml` define no deprecation field, and Go expresses deprecation
as a `// Deprecated:` comment inside `go.mod` — which the registry must not
rewrite, because that changes the file's hash and breaks every `go.sum` that
already records it. Inventing a non-standard field those clients ignore would
look like support without being it. The notice is still stored and still
readable through the API for all seven.
