ordvec 0.2.0

Training-free ordinal & sign quantization for vector retrieval
Documentation
# Releasing `ordvec`

> **Publish is held.** A real `cargo publish` / PyPI publish happens only
> on the maintainer's explicit go. CI never publishes for real — the crate job
> runs `cargo publish -p ordvec --dry-run --locked`, and the PyPI wheel is
> `publish = false` on crates.io and ships separately.

`ordvec` (the Rust crate) and `ordvec` on PyPI (the PyO3 wheel built from
`ordvec-python/`) are released by **manually dispatching** the release
workflows. Nothing ships on a tag push or a merge.

## Release pipeline controls

Both `release-crate.yml` and `release-python.yml`:

- are **`workflow_dispatch`-only** (no `push` / tag trigger);
- run a **`require-ci-green`** gate confirming the per-commit CI is green for the
  target commit on `main``ci.yml`, `fuzz.yml`, and `codeql.yml` for the crate,
  plus `python.yml` for the wheel (a *successful* run for that exact SHA on `main`);
- publish via **OIDC trusted publishing** (no long-lived crates.io / PyPI
  tokens in the repo);
- emit **SLSA build provenance** (`actions/attest-build-provenance`) **before**
  publishing — a failed attestation fails the release closed, so nothing ships
  without provenance recorded first;
- pin every third-party action by **commit SHA**, set
  `persist-credentials: false`, and default to `permissions: contents: read`.

`release-python.yml` additionally produces **PEP 740** attestations via the PyPI
Trusted Publishing step.

### Environment protection (configured in repo settings, not in code)

- **Required reviewer** — each environment (`crates-io`, `pypi`) requires
  maintainer (`Fieldnote-Echo`) approval before the publish job runs.
- **Deployment branch** — each environment is restricted to **`main`**, the
  only ref a release may be dispatched from. This makes "only `main` can
  publish" a configuration invariant rather than a manual check at approval
  time.

> These two settings are the supply-chain backstop the workflow code cannot
> express on its own (THREAT-SUPPLY-001 in [THREAT_MODEL.md]THREAT_MODEL.md).

### Tag and branch protection

- **Immutable releases** is enabled, so a published release's `v*` tag cannot be
  force-moved or deleted and its assets cannot be replaced after publication.
  This closes the GitHub-side mutability surface the registries already close on
  their end (crates.io is yank-only; PyPI burns a version on delete).
- **`main` is a protected branch** — pull-request review is required and
  force-pushes and deletions are blocked, so the branch a release dispatches
  from cannot be rewritten (THREAT-SUPPLY-002).

## Checklist

1. Land everything on `main`; confirm the working tree and `Cargo.lock` are in
   sync (`cargo build --locked`).
2. Bump the version (crate `Cargo.toml`, and `ordvec-python` if the wheel
   changed) and update `CHANGELOG.md`. Commit on `main`.
3. Confirm CI is **green for current `main` HEAD**. A release dispatches from
   `main` (the environment refuses any other ref), so `require-ci-green` always
   checks `main` HEAD's SHA — which needs a **completed, successful** (not
   cancelled, not in-progress) run of `ci.yml`, `fuzz.yml`, `codeql.yml` (and
   `python.yml` for the wheel).
   - **Do not merge another PR between the release commit and the dispatch.**
     `ci.yml` / `python.yml` use `cancel-in-progress`, so merging again moves
     `main` HEAD and cancels the previous commit's in-flight CI. The superseded
     commit is no longer the release target: **release from the new HEAD once its
     own CI has completed green** — never from, or by re-validating, the older
     commit.
   - If HEAD's *own* run shows `cancelled` (superseded, but you have since
     stopped pushing), re-run **that HEAD run** from the Actions UI and wait for
     it to finish green before dispatching. The SHA you re-run must be the exact
     SHA you publish; do not hand-clear the gate on any other commit.
   - Release only from a commit on `main` with a **successful push-to-main run**
     of each gated workflow — in practice the tip the merge produced (a squash
     commit, a rebased tip, or a merge commit), whatever the merge strategy. An
     interior commit that exists in history only from a PR branch has no
     push-to-main run (its CI ran as a `pull_request` on the branch) and so is
     not releasable.
4. Get the maintainer's explicit go to publish.
5. Dispatch `release-crate.yml` (crate) and/or `release-python.yml` (wheel)
   from **`main`**.
6. Approve the environment deployment when prompted (required reviewer).
7. Verify the published artifact (crates.io / docs.rs / PyPI) and its
   provenance, and — for a coordinated release — the Zenodo deposit.

## Coordinated release note

The crate publish, the PyPI wheel, and the paper's Zenodo deposit are
coordinated (the paper consumes the bindings for a final cold-repro run). Do
not ship one leg in isolation without the maintainer's go.