feat(release): build verified full-registry catalog candidates
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
# Full registry candidates / Vollständige Registry-Kandidaten
|
||||
|
||||
## Operator workflow (EN)
|
||||
|
||||
The canonical `release-catalog.py full-registry` command takes `--package-set`,
|
||||
`--package-lock`, `--wheelhouse`, `--webui-packages`, `--output-dir`, and a
|
||||
configured `--catalog-signing-key`. Generate the package set with
|
||||
`generate-release-package-set.py --profile full` and download its exact artifacts
|
||||
with `resolve-package-artifacts.py`; do not substitute locally rebuilt wheels.
|
||||
The candidate compares the package set with the exact developer meta-package
|
||||
pins, checks archive bytes and package metadata against the lock, and synthesizes
|
||||
every entry from its immutable tagged manifest. Native package publication and
|
||||
its CI authority remain trusted: this verifies the published artifact identity,
|
||||
not independent reproducible-build equivalence to source.
|
||||
|
||||
Pass `--selected-repository` once for each newly released repository, including
|
||||
Core when it changes. These selected units must have clean, version-aligned
|
||||
named branches whose HEAD equals the annotated local and remote release tag.
|
||||
Other full-profile packages retain their exact older annotated tags; a later
|
||||
workflow-only commit on `main` does not relabel those package contents or force
|
||||
a version bump. Every source fetch/push endpoint must match the registered
|
||||
origin, and all entries bind their source commit and annotated tag object.
|
||||
Git replacement objects, caller Git configuration, and executable-path
|
||||
redirection cannot substitute another tagged manifest tree.
|
||||
|
||||
The fixed existing website catalog and keyring are authenticated before signing.
|
||||
An older catalog without a signed keyring hash can be migrated only through this
|
||||
complete rebuild, only when its signature verifies and its entire keyring
|
||||
exactly matches the configured known signers. No old entries or artifact hashes
|
||||
are reused. The new catalog signs the exact unchanged website keyring hash;
|
||||
key rotation remains a separate reviewed operation. Selective candidates still
|
||||
reject unpinned base keyrings. New candidate directories are private and
|
||||
exclusive: a retry must choose a new directory, not overwrite a reviewed one.
|
||||
|
||||
Run these commands on the trusted host, with an operator-private source workspace
|
||||
and artifact directory. `RELEASE_PYTHON` must select its private environment and
|
||||
`RELEASE_NPM` an absolute npm executable with a trusted sibling Node 22 binary.
|
||||
In Flatpak, execute host commands through `flatpak-spawn --host`; sandbox and
|
||||
host UID mappings are not interchangeable. Do not weaken trust gates or change
|
||||
system-wide permissions.
|
||||
|
||||
```sh
|
||||
# These paths identify previously prepared private operator resources.
|
||||
RELEASE_WORKSPACE=/path/to/private/workspace
|
||||
RELEASE_PYTHON="$RELEASE_WORKSPACE/govoplan/.host-venv/bin/python"
|
||||
RELEASE_NPM=/path/to/private/node22/bin/npm
|
||||
ARTIFACT_ROOT=/path/to/private/artifacts
|
||||
RELEASE_CANDIDATE=/path/to/private/new-candidate
|
||||
RELEASE_VERSION=0.1.45
|
||||
RELEASE_TOOLS="$RELEASE_WORKSPACE/govoplan/tools/release"
|
||||
|
||||
umask 077
|
||||
"$RELEASE_PYTHON" "$RELEASE_TOOLS/generate-release-package-set.py" \
|
||||
--version "$RELEASE_VERSION" --profile full \
|
||||
--workspace "$RELEASE_WORKSPACE" --output "$ARTIFACT_ROOT/packages.json"
|
||||
PATH="$(dirname "$RELEASE_NPM"):/usr/bin:/bin" \
|
||||
"$RELEASE_PYTHON" "$RELEASE_TOOLS/resolve-package-artifacts.py" \
|
||||
--package-set "$ARTIFACT_ROOT/packages.json" \
|
||||
--wheelhouse "$ARTIFACT_ROOT/wheels" \
|
||||
--webui-packages "$ARTIFACT_ROOT/webui" \
|
||||
--lock-output "$ARTIFACT_ROOT/artifacts.lock.json" \
|
||||
--python "$RELEASE_PYTHON" --npm "$RELEASE_NPM"
|
||||
|
||||
# Repeat --selected-repository for EVERY newly released unit, not just Core.
|
||||
"$RELEASE_PYTHON" "$RELEASE_TOOLS/release-catalog.py" full-registry \
|
||||
--workspace-root "$RELEASE_WORKSPACE" \
|
||||
--package-set "$ARTIFACT_ROOT/packages.json" \
|
||||
--package-lock "$ARTIFACT_ROOT/artifacts.lock.json" \
|
||||
--wheelhouse "$ARTIFACT_ROOT/wheels" --webui-packages "$ARTIFACT_ROOT/webui" \
|
||||
--output-dir "$RELEASE_CANDIDATE" --selected-repository govoplan-core \
|
||||
--catalog-signing-key known-key=/path/to/private/known-key.pem --json
|
||||
|
||||
"$RELEASE_PYTHON" "$RELEASE_TOOLS/release-catalog.py" publish-candidate \
|
||||
--workspace-root "$RELEASE_WORKSPACE" --candidate-dir "$RELEASE_CANDIDATE" \
|
||||
--channel stable --npm "$RELEASE_NPM" --build-web \
|
||||
--commit --tag --push --tag-name "catalog-v$RELEASE_VERSION" --json
|
||||
```
|
||||
|
||||
The last command is a strict non-mutating preview because `--apply` is absent.
|
||||
Review its output, then repeat it with `--apply` to publish. The website's locked
|
||||
build dependencies must already be installed before `--build-web`. The publisher
|
||||
sanitizes the build and Git environments and pushes the verified immutable
|
||||
website commit/tag. Source tag publication and registry package availability
|
||||
must be complete before candidate generation.
|
||||
|
||||
Source tags, registry packages, and a signed module catalog do not imply that a
|
||||
new runtime distribution exists. While runtime images are held, leave the Meta
|
||||
Gitea runtime Release held too: a normal source-only Release can replace Gitea's
|
||||
`releases/latest` discovery result despite having no deployment assets. The
|
||||
deployer still requires an explicit signed manifest, digest, and trusted
|
||||
keyring; it does not deploy a tag or module catalog directly.
|
||||
|
||||
## Betriebsablauf (DE)
|
||||
|
||||
`release-catalog.py full-registry` übernimmt den vollständigen Paketbestand,
|
||||
die Registry-Sperrdatei, das Wheel-Verzeichnis, die WebUI-Archive und den
|
||||
konfigurierten Signaturschlüssel. Zuerst mit
|
||||
`generate-release-package-set.py --profile full` die exakten Meta-Paketversionen
|
||||
ermitteln und mit `resolve-package-artifacts.py` die veröffentlichten Artefakte
|
||||
herunterladen. Lokal neu gebaute Wheels sind kein Ersatz. Der Kandidat prüft
|
||||
Paketidentitäten, Dateigrößen und Hashes und erzeugt alle Einträge aus den
|
||||
unveränderlichen getaggten Manifesten. Die Registry und ihre veröffentlichende
|
||||
CI bleiben eine Vertrauensgrundlage; dies ist kein unabhängiger Nachweis eines
|
||||
reproduzierbaren Builds aus dem Quellcode.
|
||||
|
||||
Jedes neu veröffentlichte Repository wird mit `--selected-repository`
|
||||
angegeben. Nur diese Auswahl muss mit dem sauberen, versionsgleichen HEAD eines
|
||||
benannten Branches und dem annotierten lokalen und entfernten Tag übereinstimmen.
|
||||
Unveränderte Pakete behalten ihren ursprünglichen Tag, auch wenn auf `main`
|
||||
bereits eine spätere Workflow-Korrektur liegt. Alle Quelladressen müssen dem
|
||||
registrierten Ursprung entsprechen; Commit und annotiertes Tag-Objekt werden
|
||||
für jeden Eintrag gebunden. Git-Ersetzungsobjekte oder fremde Git-Konfiguration
|
||||
können dabei keinen anderen Manifestbaum unterschieben.
|
||||
|
||||
Vor dem Signieren werden der bestehende Website-Katalog und sein Schlüsselbund
|
||||
geprüft. Ein alter Katalog ohne signierten Schlüsselbund-Hash darf ausschließlich
|
||||
durch diesen vollständigen Neuaufbau migriert werden: Seine Signatur muss gültig
|
||||
sein und der gesamte Schlüsselbund exakt den konfigurierten bekannten Signierern
|
||||
entsprechen. Alte Einträge oder Artefakt-Hashes werden nicht übernommen. Der neue
|
||||
Katalog bindet den unveränderten Schlüsselbund-Hash; ein Schlüsselwechsel bleibt
|
||||
ein eigener geprüfter Vorgang. Selektive Kandidaten verlangen weiterhin einen
|
||||
bereits gebundenen Schlüsselbund. Kandidaten werden nur in neuen privaten
|
||||
Verzeichnissen erzeugt und niemals überschrieben.
|
||||
|
||||
Das obige Befehlsbeispiel wird auf dem vertrauenswürdigen Host ausgeführt. Dafür
|
||||
die private Python-Umgebung und einen absoluten `--npm`-Pfad zu Node 22 verwenden;
|
||||
unter Flatpak die Host-Werkzeuge über `flatpak-spawn --host` aufrufen. Die
|
||||
gesperrten Website-Build-Abhängigkeiten vorher installieren. Keine
|
||||
Vertrauensprüfung umgehen und keine globalen Rechte ändern. Die Artefaktordner
|
||||
müssen privat und bei der Auflösung leer sein. Vor der Kandidatenerzeugung
|
||||
müssen Quell-Tags und Registry-Pakete vollständig veröffentlicht sein.
|
||||
|
||||
Die Veröffentlichung zunächst mit `publish-candidate --commit --tag --push
|
||||
--build-web` ohne `--apply` prüfen und erst nach Prüfung mit `--apply` ausführen.
|
||||
Solange Laufzeit-Images zurückgestellt sind, bleibt auch das Meta-Gitea-Runtime-
|
||||
Release zurückgestellt: Ein reines Quellcode-Release könnte sonst als neuestes
|
||||
Release erscheinen. Eine Installation benötigt weiterhin ein signiertes
|
||||
Laufzeitmanifest, dessen Digest und einen explizit vertrauenswürdigen Schlüsselbund.
|
||||
@@ -189,8 +189,13 @@ than invoking `pip`, `npm`, or Git on the target host.
|
||||
|
||||
## Public module directory
|
||||
|
||||
`tools/release/publish-release-catalog.sh` resolves the selected package set and
|
||||
registry lock before it creates a catalog. Catalog entries are synthesized from
|
||||
For an operator-reviewed full publication, use
|
||||
`tools/release/release-catalog.py full-registry` followed by the same tool's
|
||||
`publish-candidate` command. Resolve the package set and registry lock first;
|
||||
the older direct-write shell wrapper is not the strict candidate publication
|
||||
path. See [Full registry candidates / Vollständige Registry-Kandidaten](FULL_REGISTRY_CANDIDATES.md)
|
||||
for the private host runtime, exact artifact checks, and legacy keyring transition.
|
||||
Catalog entries are synthesized from
|
||||
the exact tagged module manifests, never from a hand-maintained module list or
|
||||
the current workspace. Each entry binds its Python wheel and optional WebUI
|
||||
tarball to the registry URL, filename, size, SHA-256, package identity, source
|
||||
|
||||
@@ -517,6 +517,15 @@ 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`.
|
||||
|
||||
For a full registry-backed release, first build a fresh private candidate using
|
||||
`release-catalog.py full-registry`. Pass `--selected-repository` for newly
|
||||
released HEAD-bound units, not every unchanged package in the full profile.
|
||||
The command independently checks all full-profile registry bytes and annotated
|
||||
tag provenance, then feeds this same strict `publish-candidate` transaction.
|
||||
It does not create Gitea runtime Releases or dispatch image builds. See
|
||||
[Full registry candidates / Vollständige Registry-Kandidaten](FULL_REGISTRY_CANDIDATES.md)
|
||||
for the complete EN/DE workflow and the narrowly scoped legacy keyring transition.
|
||||
|
||||
Published channels are expected below the public catalog base URL:
|
||||
|
||||
- `https://govoplan.add-ideas.de/catalogs/v1/channels/stable.json`
|
||||
|
||||
Reference in New Issue
Block a user