# Stable, legacy and release-candidate metadata

The v1.3 client requires authenticated schema-v2 metadata. The existing
`https://mpv.rocks/api/releases.json` currently serves legacy unsigned v1.2
metadata. Adding a trusted key to a build alone cannot make that document valid.

## Endpoint contract

| Path | Consumers | Publication rule |
| --- | --- | --- |
| `/api/releases.json` | v1.1/v1.2 clients and public website downloads | Stable only. Keep the existing v1.2 document until a reviewed stable release is published. |
| `/api/releases/stable.json` | New stable builds | Signed schema-v2 document, `channel: stable`; prerelease versions rejected. |
| `/api/releases/rc.json` | New prerelease builds | Signed schema-v2 document, `channel: rc`; never changes either stable endpoint. |

Version `1.3.0-rc.12` automatically selects the RC feed; `1.3.0` selects stable.
Build metadata such as `+local-build` does not change the channel. Normal SemVer
ordering compares `rc.2` below `rc.10`, and both below the final release.

RC and stable manifests must match the build's channel. There is no unsigned
fallback. Replay state for stable keeps its existing filename; RC uses
`release-trust-state-rc.json`, so testing a candidate does not advance stable's
publication/version high-water mark. Both channels retain signature, schema,
key epoch/revocation, freshness and anti-rollback checks.

To graduate testers to final, generate and sign a second announcement for the
same final artifacts with `-channel rc`. The RC document may advertise a final
version. Once installed, the final binary automatically uses the stable feed.
Publish the stable announcement first. This requires an explicit approved
promotion in the signer/publisher; ordinary final-tag CI does not automatically
advance RC. Never relabel RC binary bytes as final: binary identity must match
its manifest version.

## Build and generation

`make build-all-parallel` now requires `MANIFEST_PUBLIC_KEYS` and
`MANIFEST_KEY_VALIDITY`, like release builds, before creating distributable
binaries. Missing trust previously produced the owner's unusable QA builds.
Individual development build targets still support empty trust for offline
coding; their network install/update operations fail closed.

The default Make version is now `1.3.0-rc.12`. Prerelease Make builds load the
public RC key and validity epoch from `release/rc-trust.mk`; stable builds load
the separate production ring from `release/stable-trust.mk`. Explicit build
variables override either public ring. No private key is in the repository.

For manual QA, use the existing `dist` executables or the published
[RC archives](https://mpv.rocks/api/releases/artifacts/v1.3.0-rc.9/SHA256SUMS.txt).
They match the live manifest exactly. `make build-all-parallel` embeds the RC
trust automatically, but rebuilding produces a new build timestamp: the client
rejects a manifest more than five minutes older than its build. A later rebuild
therefore needs newly reviewed and signed metadata before network installation
QA. Do not change the timestamp or disable freshness checks to bypass this.
Changed release binaries require a new immutable candidate version and matching
publication; never overwrite a published candidate with a later local build.

Existing `dist` files are not replaced if a build prerequisite fails; check
build success and binary identity rather than reusing an old executable.

`generate-info -auto` requires an exact manager version, reviewed provenance
lock and local manager artifact directory. It infers stable/RC from the version;
`-channel rc` also supports an approved final announcement to RC testers.
`verify-manifest -expected-channel stable|rc` verifies destination identity in
addition to signature/schema/version.

CI routes prerelease publication through separate protected
`RC_MANIFEST_PUBLISH_URL`, `RC_MANIFEST_PUBLISH_TOKEN` and
`RC_MANIFEST_VERIFY_URL` variables. Missing RC values fail; stable credentials
are not inherited. The publisher must independently restrict the RC credential
to RC even if a caller changes an informational channel header. Public read-back
must match the exact signed bytes, expected channel and version.

## Static publisher

`cmd/publish-manifest` installs an **already signed** document in a static API
root. It holds no signing key and provides no unauthenticated HTTP endpoint.
Run an independently reviewed/pinned build behind the publisher's authenticated
operation boundary, or invoke it manually through an authorized SSH session.
The operator supplies the authorized destination explicitly:

```sh
go build -o dist/publish-manifest ./cmd/publish-manifest
MANIFEST_PUBLIC_KEYS='approved-key-id=BASE64_PUBLIC_KEY' \
  ./dist/publish-manifest -manifest /private/inbox/rc.json \
  -channel rc -api-dir /path/to/site/api
```

The tool verifies signature/schema/channel, bounds document size, rejects
future timestamps, rollbacks and same-time equivocation, and serializes writes
across processes. Existing signed feeds must still verify under the configured
trust ring (retain retiring public keys during rotation). It atomically
replaces individual JSON files. Stable publication also mirrors the same signed
bytes to `releases.json`, retaining legacy URL/BLAKE3 and `MpvVersion` fields.
If interrupted between stable and legacy writes, legacy retains the prior
stable version; an idempotent retry repairs the mirror. RC publication never
reads or writes the legacy endpoint.

Immutable artifact/version storage, independently approved provenance,
authorized signing and HTTP request authentication remain separate release
infrastructure requirements. The tool does not implement those services or
replace those gates.

## Linode deployment — updated 2026-09-07

The owner-approved RC publication now serves **1.3.0-rc.9**, application commit
`8a75f4e`, build time `2026-09-07T23:58:14Z`. The authenticated
[RC feed](https://mpv.rocks/api/releases/rc.json) and six immutable executable/archive
pairs are live. RC9 includes the Windows UX remediation, fast-completion retry
fix and SSE cleanup/reconnection across cached browser navigation; use it for
new QA. Earlier candidate artifacts remain immutable evidence.

- SSH alias: `linode`; static API root: `/sites/mpv.rocks/api`.
- Backups: `/root/mpv-release-migration-20260906` (root-only).
- Tools remain pinned at `/opt/mpv-release-tools/708bfc2`.
- Reviewed inputs: `/var/lib/mpv-release-signer/v1.3.0-rc.9`, using
  `release/provenance-v1.3.0-rc.9.json`. All 16 cached upstream assets were
  rechecked against their reviewed SHA256, BLAKE3 and size; the existing six
  Windows mpv/FFmpeg mirrors retain their original reviewed bytes.
- Manual signer: `mpv-sign-reviewed-release@1.3.0-rc.9.service`, using the
  repository's `deploy/` unit/script. It runs as `mpv-manifest-signer`, offline,
  with read-only reviewed inputs and a writable output directory. The private
  seed remains on Linode at `/var/lib/mpv-release-keys/rc-2026-09.seed` (0600),
  supplied through systemd credentials. It was never copied locally.
- Public trust: key `rc-2026-09`, ring/epoch in `release/rc-trust.mk`. The
  publisher independently verifies the signature before atomically replacing
  `/api/releases/rc.json`.
- Immutable artifacts: `/api/releases/artifacts/v1.3.0-rc.9/`, directory 0555,
  files 0444. The API parent retains sticky group write permission for the
  legacy worker; no nginx changes were required.
- The legacy `/sites/apps/generate-info` and separate statistics use remain
  intact. Its service override still pins manager version 1.2.0, its timer is
  active, and all RC7–RC9 publication operations preserved the legacy feed
  bytes. `/api/releases/stable.json` remains absent.

The public feed matched the signed output byte-for-byte. All six downloaded
public executables matched local SHA256, signed BLAKE3 and size. RC7's native
busy-player QA preserved the installation but exposed a late-callback retry
race. RC8 fixed it, then its full Edge route sweep exposed cached pages retaining
SSE connections. RC9 fixes that leak and passes the repeated native route sweep
and cached Back navigation. See the
[RC9 Windows UX report](qa/2026-09-07/WINDOWS_UX_RC9.md) and
[artifact/validation evidence](qa/2026-09-07/results-rc9.json).

This administrator-invoked RC operation does not complete independent review,
short-lived GitLab authorization, protected tags, final attestation or owner
release acceptance. No Git push or final stable release occurred.

## Reviewed upstream mirrors

Upstream release filenames can be replaced: during RC4 QA, GitHub asset
`546513057` was replaced by `547169280` at the same mpv URL. The downloaded
bytes correctly failed the reviewed BLAKE3 check. RC4 therefore serves the six
reviewed Windows mpv/FFmpeg archives from immutable
`/api/releases/upstream-artifacts/<blake3>/<filename>` paths. The original
upstream URL, release asset ID, SHA256 and BLAKE3 remain in the provenance record.

An optional provenance artifact `download_url` must be HTTPS and include the
reviewed BLAKE3 digest as a complete path segment. Generation first verifies
cached bytes against their source URL and pinned digest, then substitutes the
reviewed download URL without changing hashes. Unused mirrors and mismatched
digests fail before signing. Publish and independently verify the mirrored bytes
before signing metadata that references them. The private signing job stays
offline; it does not retrieve or authorize new upstream content.

## Next candidate and stable cutover

For another RC, prepare a new root-owned version directory with exact reviewed
provenance, cached upstream assets, matching manager artifacts and `job.env`.
Pin the tool directory and immutable download base. Run the corresponding
signing unit, independently verify the output, publish the version's artifacts,
then invoke `publish-manifest -channel rc` with the configured public ring.
Compare public bytes and run the actual app/install checks before QA handoff.
Preserve the previous immutable artifacts and signed manifest as evidence.
Do not turn this into a timer that signs unchecked latest upstream releases.

The new `/api/releases/stable.json` is intentionally not populated yet. Before
the first approved signed stable publication:

1. Finish stable trust/provenance, final artifact QA and publication approval.
2. Stop the release-generation timer and drain its service. Preserve the old
   legacy document/executable for recovery; keep repo-stats independent.
3. Publish reviewed stable metadata and its legacy mirror using the static
   publisher. Prevent the old timer from overwriting the signed mirror.
4. Verify old/new consumers and exact public bytes, then explicitly promote
   the final artifacts to the RC feed as described above.

Broader owner manual acceptance and production release gates remain in
[release readiness](RELEASE_READINESS_v1.3.0.md).

## Separate stable signing job

`deploy/mpv-sign-reviewed-stable@.service` uses the stable key credential and
selects `channel=stable`; prerelease versions are refused. Its root-owned job
configuration, reviewed provenance, cached upstream bytes and manager artifacts
follow the same layout as the RC signer. Signing produces only a private output
file. It does not publish either feed or upload package archives.

Keep the signing unit distinct from publication approval. Verify the private
signed output with the independently configured stable public ring before
publication. The legacy generator continues running until the approved stable
cutover. An offline, administrator-started job does not satisfy the automated
OIDC/independent-approval CI contract; that external integration remains a
separate deployment gate.
