self_update 1.1.0

Self updates for standalone executables
Documentation
# self_update — AI Agent Instructions

## Contributing Guidelines
Before making any changes, read and follow **[CONTRIBUTING.md](CONTRIBUTING.md)**.
Key points:
- Run `cargo fmt` before committing.
- Update `CHANGELOG.md`'s `[unreleased]` section with a description of what changed and why.
- After editing `src/lib.rs`, regenerate the README with `./readme.sh` and verify with `./readme.sh check``README.md` is generated, never edit it directly.
- Run the verification steps below before submitting.

## Git Push Protocol
Before every `git push`, show a diff summary so the user can see exactly what is going up:

```bash
git log origin/BRANCH..HEAD --oneline   # commits being pushed
git diff origin/BRANCH --stat           # files changed
```

Follow with a one-sentence summary (e.g. "Pushing 2 commits touching src/update.rs and CHANGELOG.md"). Then push.

---

## Temp Files & Working Notes
Write all temporary, scratch, and working files to `local/` — scratch scripts, research
dumps, intermediate agent outputs, throwaway probe crates, **and planning/design docs**. The
`local/` directory is committed (via `local/.gitkeep`) but **all of its contents are
gitignored**, so anything inside is safe to write and never shows up in a commit or PR. Do not
create temp/working/plan files anywhere else in the repo.

---

## Specifications (canonical behavior reference)
`specs/` is the committed source of truth for what the crate does. The `specs/ref-*.md` files
document the current behavior of each subsystem (pipeline, backends, http client, errors,
features, etc.), cited to `file:line`, each ending with an invariants/regression checklist. The
remaining specs record implemented decisions and deferred/needs-research work. See
[specs/README.md](specs/README.md) for the index.

Use the specs as the reference for defining existing and new functionality, evaluating a change,
and detecting regressions:
- Before changing a subsystem, read its `ref-*` spec for the current contract and the invariants
  a change must preserve. If the code and a `ref-*` spec disagree, one of them is a bug.
- When a change alters behavior, update the matching `ref-*` spec in the same change (the same
  discipline as keeping `README.md` and `CHANGELOG.md` in sync).
- When adding functionality, write or extend the relevant `ref-*` spec; move a deferred item to
  `implemented` once it ships.

---

## Project Overview
`self_update` is a Rust crate providing updaters that replace the running executable in-place
from a release-distribution backend. It is a **single crate** (the version lives only in the
top-level `Cargo.toml`).

Release backends, each with an `Update` (configure → build → update) and a `ReleaseList`
builder:
- **github**`https://api.github.com/repos/<owner>/<repo>/releases` (or a custom/enterprise URL)
- **gitlab**`<host>/api/v4/projects/<owner>%2F<repo>/releases`
- **gitea**`<host>/api/v1/repos/<owner>/<repo>/releases`
- **s3** — Amazon S3 / GCS / DigitalOcean Spaces / any S3-compatible endpoint

---

## Public API conventions (1.0+)
The `1.0.0` release stabilised the public surface; keep new code consistent with it:
- **Builder vocabulary is unified, no `with_` prefix.** Custom endpoint is `url(...)` on every
  git backend; the `ReleaseList` target filter is `filter_target(...)`; s3 credentials are `access_key(...)`. Common
  setters (`current_version`, `target`, `asset_identifier`, `bin_name`, `bin_install_path`,
  `bin_path_in_archive`, `show_download_progress`, `progress_style`, `show_output`,
  `no_confirm`, `auth_token`, `verify_keys`) are generated once by the
  `impl_common_builder_setters!` macro — add or change a shared setter there, not per backend.
- **Shared config lives in `src/backends/common.rs`** (`CommonBuilderConfig` → validated
  `CommonConfig`). Each backend's `UpdateBuilder`/`Update` embeds a `common` field plus only its
  backend-specific fields. The shared `ReleaseUpdate` accessors are generated by
  `impl_release_update_accessors!` (reads through `self.common`).
- **`ReleaseUpdate` is a sealed trait** (`update::sealed::Sealed`) — callable by downstream, not
  implementable. Its accessors return borrows (`&str` / `Option<&str>`), not owned `String`.
- **`Error` is `#[non_exhaustive]`**; the active http-client error is the opaque `Error::Transport`
  and the `s3-auth` signing errors are the opaque `Error::S3Auth` (underlying error via
  `Error::source()`). `VersionStatus`/`ArchiveKind`/`Compression`/`ReleaseStatus`/`Release`/`ReleaseAsset`
  are also `#[non_exhaustive]`.
- The `http` crate is re-exported as `self_update::http`; `zipsign_api` and the `VerifyingKey`
  alias are re-exported under the `signatures` feature.
- Breaking changes are only acceptable in a major bump. Migration guidance for `0.x → 1.0` lives
  in `docs/migrations/0.x-to-1.0.md` (agent/automation) and `docs/migrations/0.x-to-1.0-human.md`
  (human).

---

## HTTP client & TLS
Both `reqwest` and `ureq` may be enabled at the same time; the sync API prefers `reqwest`
when both are on. To use `ureq` alone, set `default-features = false, features = ["ureq", ...]`.
A build with **neither** client is a hard `compile_error!` in `src/http_client/mod.rs`.

TLS backends (`rustls` default, `native-tls`) may also coexist; `rustls` wins when both are
enabled. `cargo build --all-features` (both clients + both TLS backends) builds.

---

## Build
```bash
cargo build                                   # default: reqwest + rustls
# full optional feature set (reqwest):
cargo build --features "github gitlab gitea s3 archive-tar archive-zip compression-tar-gz compression-zip-deflate compression-zip-bzip2 signatures s3-auth checksums"
# the ureq client:
cargo build --no-default-features --features "ureq native-tls github gitlab gitea s3 archive-tar archive-zip compression-tar-gz compression-zip-deflate compression-zip-bzip2 signatures s3-auth checksums"
```

## Format
```bash
cargo fmt              # apply
cargo fmt --check      # verify only
```

## Lint
The Makefile is the source of truth for the verification lanes; prefer `make check/clippy`
(runs all three lanes) over the raw commands. If the raw commands below ever disagree with the
Makefile's `REQWEST_FEATURES` / `UREQ_FEATURES` / `ASYNC_FEATURES`, trust the Makefile.
```bash
# reqwest lane:
cargo clippy --all-targets --features "github gitlab gitea s3 archive-tar archive-zip compression-tar-gz compression-zip-deflate compression-zip-bzip2 signatures s3-auth checksums"
# ureq lane:
cargo clippy --all-targets --no-default-features --features "ureq native-tls github gitlab gitea s3 archive-tar archive-zip compression-tar-gz compression-zip-deflate compression-zip-bzip2 signatures s3-auth checksums"
# async lane (reqwest + async):
cargo clippy --all-targets --features "async github gitlab gitea s3 archive-tar archive-zip compression-tar-gz compression-zip-deflate compression-zip-bzip2 signatures s3-auth checksums"
```

## Test
Tests live in three places: in-module `#[cfg(test)] mod tests` blocks in `src/…` (including the
backend modules), doctests in `src/lib.rs` and the backend modules, and integration tests in
`tests/` (`custom_transport.rs`, `error_helpers_external.rs`) that exercise the crate through its
public API. No external services are required. Prefer `make tests` (runs the default, reqwest,
ureq, and async lanes); the raw commands are a fallback:
```bash
# reqwest lane:
cargo test --features "github gitlab gitea s3 archive-tar archive-zip compression-tar-gz compression-zip-deflate compression-zip-bzip2 signatures s3-auth checksums"
# ureq lane:
cargo test --no-default-features --features "ureq native-tls github gitlab gitea s3 archive-tar archive-zip compression-tar-gz compression-zip-deflate compression-zip-bzip2 signatures s3-auth checksums"
# async lane (reqwest + async):
cargo test --features "async github gitlab gitea s3 archive-tar archive-zip compression-tar-gz compression-zip-deflate compression-zip-bzip2 signatures s3-auth checksums"
```

## README Sync
`README.md` is auto-generated from `src/lib.rs` doc comments via `cargo-readme` — **never edit `README.md` directly**.
```bash
./readme.sh         # regenerate (cargo readme --no-indent-headings > README.md)
./readme.sh check   # verify in sync
```

---

## Mandatory Verification After Every Change
The single command that runs everything CI enforces is **`make ci`** — fmt, README drift check,
clippy and tests across every lane (reqwest, ureq, async), the `--all-features` build, and every
backend example build. This is exactly what `.github/workflows/build.yml` runs, so a green
`make ci` locally means green CI. Prefer it over running the steps by hand.

If you do run the steps individually, run these in order and do not present the change as complete
until all pass:
1. **Format**`cargo fmt`
2. **Lint**`cargo clippy` on every lane: reqwest, ureq, async (commands above), or `make check/clippy`
3. **Test**`cargo test` on every lane: reqwest, ureq, async (commands above), or `make tests`
4. If `src/lib.rs` changed — `./readme.sh && ./readme.sh check`, or `make check/readme`

The `pr-cycle` skill's helper also bundles all of this: `.agents/skills/pr-cycle/pr.py 0 ci`.
Run `make help` to list all targets (e.g. `make check`, `make tests/ureq`, `make examples/s3`,
`make docs/readme`).

---

## Fixes Require Tests
Any code fix — from a PR review finding, a reported bug, or an internal audit — **must be accompanied by a test** that fails without the fix, passes with it, and guards against regression. Add it in the relevant module's `#[cfg(test)] mod tests` block (or a doctest in `src/lib.rs`), in the same change as the fix.

---

## Agent Skills

Agent-agnostic skills live in **`.agents/skills/`** (the canonical copy). `.claude/skills/`
symlinks into it so Claude Code picks them up; other agents can read the `SKILL.md` files
directly. Invoke via `/skill-name` in Claude Code or by name in agent prompts.

| Skill | Path | When to use |
|---|---|---|
| `pr-review` | `.agents/skills/pr-review/SKILL.md` | Read-only review of a PR/branch (code + consumer sub-agents) |
| `pr-cycle` | `.agents/skills/pr-cycle/SKILL.md` | Full review → fix → push → resolve → re-request loop on an open PR (modes: `full` / `local` / `remote`) |
| `release` | `.agents/skills/release/SKILL.md` | Bump the version, update CHANGELOG + migration guide, regenerate README — or run a pre-release review |
| `consumer-experience-review` | `.agents/skills/consumer-experience-review/SKILL.md` | Evaluate the public API surface from a downstream crate-author perspective |

Claude sub-agent definitions used by these skills live in `.claude/agents/`
(`pr-code-reviewer`, `pr-consumer-reviewer`, `pr-fix-implementer`).

---

## Key Cargo Features

| Feature | Description |
|---|---|
| `reqwest` (default) | reqwest http client backend |
| `ureq` | ureq http client backend; may coexist with `reqwest` (reqwest is preferred for sync when both are present) |
| `rustls` (default) | rustls TLS for the selected client |
| `native-tls` | native/OpenSSL TLS for the selected client |
| `progress-bar` (default) | indicatif terminal progress bar |
| `github` (default) | gate the GitHub release backend |
| `gitlab` | gate the GitLab release backend |
| `gitea` | gate the Gitea release backend |
| `s3` | gate the S3 release backend (`s3-auth` implies this) |
| `archive-tar` | tar archive support |
| `archive-zip` | zip archive support |
| `compression-tar-gz` | gzip compression (tar.gz) |
| `compression-zip-deflate` | zip deflate compression |
| `compression-zip-bzip2` | zip bzip2 compression |
| `signatures` | verify `.zip`/`.tar.gz` artifacts via zipsign |
| `checksums` | verify a downloaded artifact against a known digest (sha2) |
| `s3-auth` | sign S3 requests (AWS SigV4) for private buckets |
| `async` | `*_async` update verbs; requires `reqwest`; `ureq` may coexist |

---

## Important Paths

| Path | Purpose |
|---|---|
| `src/lib.rs` | Crate entry + doc comments (source of truth for README); `Download`, `Extract`, `Move`, `Status`, `ArchiveKind`, `get_target`, re-exports, `compile_error!` feature guards |
| `src/update.rs` | Shared update flow, `Release` / `ReleaseAsset`, the sealed `ReleaseUpdate` trait (`sealed` module) |
| `src/backends/mod.rs` | Backend module exports + shared helpers (e.g. `Link`-header pagination) |
| `src/backends/common.rs` | `CommonBuilderConfig` / `CommonConfig` — fields shared by every backend's `Update` |
| `src/backends/{github,gitlab,gitea,s3}.rs` | Per-backend `Update` / `ReleaseList` builders (backend-specific fields + `common`) |
| `src/http_client/{mod,reqwest,ureq}.rs` | Pluggable http client abstraction |
| `src/macros.rs` | Crate macros (`cargo_crate_version!`; `impl_common_builder_setters!` + `impl_release_update_accessors!` for the shared backend surface; internal helpers) |
| `src/version.rs` | Semver comparison helpers |
| `src/errors.rs` | `Error` / `Result` types (`#[non_exhaustive]`; opaque `Transport` / `S3Auth` variants) |
| `examples/` | Runnable usage examples (`github`, `gitlab`, `gitea`, `s3`, `custom`, `embedded_key`) |
| `tests/` | Integration tests exercising the public API (`custom_transport.rs`, `error_helpers_external.rs`) |
| `Makefile` | Source of truth for the CI lanes; `make ci` runs everything CI enforces. `make help` lists targets |
| `readme.sh` | README generation/check wrapper around `cargo-readme` |
| `CHANGELOG.md` | Keep-a-changelog style; always has an `[unreleased]` section on top |
| `docs/migrations/` | Per-release migration guides; `PREV-to-X.Y.Z.md` (agent/automation) and `PREV-to-X.Y.Z-human.md` (human). Current: `0.x-to-1.0.md` / `0.x-to-1.0-human.md` |
| `specs/` | Canonical behavior reference (`ref-*.md`, cited to `file:line`) plus decision and deferred-work specs. Read before changing a subsystem; update when behavior changes. Index in `specs/README.md` |
| `local/` | Gitignored scratch space — use for any temp/intermediate files |