Document artifact origin and installer receipts

This commit is contained in:
2026-07-22 20:42:35 +02:00
parent 9c0650b2ed
commit 4e42911477
2 changed files with 131 additions and 36 deletions

View File

@@ -212,7 +212,9 @@ an alternative condition without `required_scopes` or `any_scopes`.
The catalog workflow panel can also operate on the same selected rows:
- `Generate` creates a signed candidate below `runtime/release-candidates/`.
- `Generate` creates a signed candidate in the operator's private XDG state
directory and records only an opaque candidate ID plus the canonical catalog
SHA-256 in durable run state.
- `Preview` validates a candidate and shows what would be copied into the
website repository.
- `Apply + Website Tag` requires `APPLY` in the confirmation field.
@@ -241,6 +243,30 @@ cannot be applied or published while any referenced source tag is absent or
inconsistent; the preview and API response list each repository and missing or
invalid ref so the operator can repair the exact source releases first.
Every selected Python release must also supply its exact built wheel. Candidate
generation computes the archive SHA-256 and an install-stable payload identity
from one bounded, regular-file descriptor and signs those values into
`release.artifacts`. Updating a Python version without a matching wheel removes
the stale identity. A source-only preview can still explain the gap, but apply,
commit, tag, and push fail closed until every selected Python unit has a matching
built-artifact identity. Repositories selected only for a meta/source tag do not
need a Python artifact.
Candidate directories and files are operator-owned `0700`/`0600` state. Before
any later release step consumes one, the executor re-resolves the opaque handle
below the configured root and re-hashes the signed catalog against its persisted
receipt. Shared roots, symlinks, channel path fragments, altered candidates, and
unowned files are rejected.
The server-owned wheel builds remain under the private candidate's `artifacts/`
directory. This slice signs their identities but does **not** upload the wheel or
add a download URL to the public catalog; the existing Git `python_ref` is source
provenance and rebuilding it is not an equivalent artifact. Keep the private
candidate until the exact wheels have been transferred through an approved
deployment channel, verified against `archive_sha256`, consumed by the installer,
and covered by its signed receipt. Public artifact transport and receipt-aware
candidate cleanup remain separate release-lifecycle work.
Read-only provenance checks derive a same-host HTTPS Git URL from conventional
SSH remotes when possible, with a non-interactive check of the configured remote
as fallback. Source tag creation and atomic publication always use the explicit
@@ -269,18 +295,28 @@ Build a signed selective catalog candidate:
```sh
KEY_DIR="$HOME/.config/govoplan/release-keys"
ARTIFACT_DIR="$(mktemp -d)"
../govoplan-files/.venv/bin/python -m pip wheel \
--no-deps \
--no-build-isolation \
--wheel-dir "$ARTIFACT_DIR" \
../govoplan-files
./.venv/bin/python tools/release/release-catalog.py selective \
--repo-version govoplan-files=0.1.9 \
--python-artifact \
"govoplan-files=$ARTIFACT_DIR/govoplan_files-0.1.9-py3-none-any.whl" \
--channel stable \
--catalog-signing-key "release-key-1=$KEY_DIR/release-key-1.pem"
```
This writes candidate catalog/keyring files below `runtime/release-candidates/`,
generates the browsable module directory below `modules/`, validates the signed
candidate with the module installer validator, and reports whether the candidate
catalog/keyring match the currently published channel. It does not publish to
the website repository.
This writes candidate catalog/keyring files below
`$XDG_STATE_HOME/govoplan/release-candidates/` (or
`~/.local/state/govoplan/release-candidates/`), generates the browsable module
directory below `modules/`, validates the signed candidate with the module
installer validator, and reports whether the candidate catalog/keyring match the
currently published channel. It does not publish to the website repository.
Regenerate the browsable module directory from an existing catalog/keyring:
@@ -295,8 +331,10 @@ Regenerate the browsable module directory from an existing catalog/keyring:
Preview publication of a reviewed candidate:
```sh
CANDIDATE_ROOT="${XDG_STATE_HOME:-$HOME/.local/state}/govoplan/release-candidates"
./.venv/bin/python tools/release/release-catalog.py publish-candidate \
--candidate-dir runtime/release-candidates/stable-YYYYMMDD-HHMMSS \
--candidate-dir "$CANDIDATE_ROOT/stable-YYYYMMDD-HHMMSS" \
--channel stable
```
@@ -304,7 +342,7 @@ Apply the reviewed candidate into the website repository without pushing:
```sh
./.venv/bin/python tools/release/release-catalog.py publish-candidate \
--candidate-dir runtime/release-candidates/stable-YYYYMMDD-HHMMSS \
--candidate-dir "$CANDIDATE_ROOT/stable-YYYYMMDD-HHMMSS" \
--channel stable \
--apply \
--commit \
@@ -315,7 +353,7 @@ Push is a separate explicit flag:
```sh
./.venv/bin/python tools/release/release-catalog.py publish-candidate \
--candidate-dir runtime/release-candidates/stable-YYYYMMDD-HHMMSS \
--candidate-dir "$CANDIDATE_ROOT/stable-YYYYMMDD-HHMMSS" \
--channel stable \
--apply \
--commit \
@@ -323,6 +361,13 @@ Push is a separate explicit flag:
--push
```
Publication validates the same in-memory catalog object that it writes; it does
not copy a path that can change after validation. On commit, the console reads
the catalog, keyring, and complete module directory back from the immutable Git
tree and requires byte-for-byte equality with those validated objects. Tags and
remote branch updates then reference that exact commit SHA rather than the
mutable worktree `HEAD`.
Published channels are expected below the public catalog base URL:
- `https://govoplan.add-ideas.de/catalogs/v1/channels/stable.json`