dist upgrade --latest: Update aus dem Gitea-Feed in einem Befehl #161

Closed
opened 2026-09-28 16:52:36 +00:00 by torben · 3 comments
Owner

Teilpaket von #140. D17 ist entschieden (Betreiber, 2026-09-28).

Stand

Abgeschlossen am 2026-09-29 im Kandidaten 7.1.0-beta.32 (Richtung 8.0.0, #140 D19). dist upgrade --latest [--expect <version>] lädt das neueste Release aus dem Feed, prüft es und spielt es über den bestehenden Upgrade-Pfad ein.

Geprüft: pytest in tools/ komplett grün (1579 Tests, danach test_dist_upgrade.py mit 64 Tests nach dem letzten Zusatztest), docs verify und instructions verify grün. CI grün auf cf89231 (Läufe 427 und 428) und auf dem Doku-Nachzug 94deccb (Lauf 429). Gegen den echten Gitea-Feed wurde --latest nicht ausgeführt: Das Ursprungs-Repo hat keinen Stamp, dist upgrade verweigert dort. Der erste echte Lauf ist das Update einer Instanz auf 8.0.0.

Warum

Echte Installationen beziehen ihre Updates aus dem Gitea-Feed (#140 D7). Vorher lud dist upgrade nichts selbst herunter („Never downloads anything“). Der Agent musste Tarball und .sha256 vorher holen, und zwar genau mit den Shell-Konstrukten (curl, sha256sum, tar), die #153 aus den Anleitungen entfernt. Unter Windows hätte er dafür zusätzlich pwsh-Syntax gebraucht. version check kannte den Feed bereits: aus dem Stamp oder aus WIKITOOL_UPDATE_URL.

Ausgangslage vor der Umsetzung

  • Feed: version.update_url() (Env → Stamp → DEFAULT_UPDATE_URL, immer …/releases/latest). version._fetch_latest_object() liefert das ganze Release-Objekt aus einer Anfrage. fetch_latest_notes begründet, warum nie eine /releases/tags/<tag>-URL zusammengesetzt wird.
  • Feed-Antwort (live geprüft, v7.0.0): assets[] mit name und browser_download_url, z. B. chemenu-stack-7.0.0.tar.gz und ….tar.gz.sha256. tag_name trägt ein v, das Version.parse akzeptiert. Den Namen setzt .gitea/workflows/release.yml (name="chemenu-stack-${VERSION}").
  • Upgrade-Pfad: run_upgrade() prüft zuerst die lokalen Vorbedingungen, dann löst _resolved_source() die Quelle auf (Sidecar-Prüfung, Entpacken in ein TemporaryDirectory). fail() wirft typer.Exit; with-Blöcke räumen also auch im Fehlerfall auf.

Entscheidungen

  • E1 Name: dist upgrade --latest. <source> ist optional; genau eines von beiden ist Pflicht. Beides oder keins ergibt Exit 1 vor jeder Prüfung.
  • E2 Kein --version <v>. Eine beliebige Version zu holen hieße, eine Tags-URL aus der latest-URL zu raten (Invariante 7). Ein Downgrade wird ohnehin verweigert; eine andere Version als die neueste hat keinen Anwendungsfall.
  • E3 Reihenfolge: lokale Vorbedingungen → Feed abfragen → Feed-Version gegen --expect und VERSION prüfen, vor jedem Download (gleich → No-op „Already at …“, älter → Downgrade-Ablehnung, Pre-Release ohne --pre → Ablehnung) → Asset-Paar im Release-Objekt suchen, fehlt eines → Exit 1 vor dem ersten Download → beide in ein TemporaryDirectory(prefix="wikitool-upgrade-") laden → Summe prüfen → bestehender Pfad. Zusätzlich: VERSION im Tarball muss der Feed-Version entsprechen, sonst Exit 1 und nichts wird angewendet.
    Die „strikte“ Summenprüfung brauchte keinen eigenen Modus in _verify_sha256_sidecar: auf diesem Pfad ist die Summe immer vorhanden, weil ihr Fehlen schon bei der Asset-Suche abgelehnt wird.
  • E4 Asset-Suche nach exaktem Namen über das assets-Feld des schon geholten Release-Objekts; die URL kommt aus browser_download_url, nie zusammengesetzt. Die Fehlermeldung nennt die vorhandenen Assets und die Release-Seite. Der Name steht als version.ARCHIVE_NAME in Python; test_release_asset_names_match_the_workflow hält ihn mit release.yml zusammen.
  • E5 Token nur an denselben Origin (Schema, Host, Port; Default-Ports normalisiert), und dann als unredirected Header, damit ein Redirect ihn nicht mitnimmt.
  • E6 Kein https-Zwang. Die sha256 kommt vom selben Host und schützt nur vor Übertragungsfehlern; die Echtheit hängt am Vertrauen in den Feed-Host. Steht in INSTALL.md, in upgrade-instance.md Schritt 4 und im Record.
  • E7 Netzcode in version.py: fetch_latest_assets/LatestRelease, token_for_asset, download_asset (gestreamt, 60 s Socket-Timeout, Fehler als VersionError mit URL). Der Docstring von fetch_latest nennt drei Befehle.
  • E8 --dry-run lädt, prüft und klassifiziert, schreibt nichts und räumt auf.
  • E9 Nicht Teil dieses Pakets: Die upstream merge-Verweise im Record und in der fail()-Meldung von run_upgrade bleiben für #153; ebenso der Neuschnitt von INSTALL.md.
  • E10 (Betreiber 2026-09-29, Variante B) --expect <version>, nur mit --latest: meldet der Feed eine andere Version, Exit 1 vor dem Download, Meldung nennt beide Versionen und verweist auf version notes. Verglichen wird die geparste Version (v-Präfix egal, -beta.N zulässig). upgrade-instance.md übergibt die Version aus Schritt 2 in Dry-Run und echtem Lauf. Die Ablehnungstexte für lokal geänderte Dateien nennen auf diesem Pfad dist upgrade --latest --expect <version> … als Wiederholungszeile.

Umgesetzt

  1. tools/chemenu/version.py: Asset-Suche, Download, Origin-Regel, Namenskonstante, Docstring.
  2. tools/chemenu/commands/dist_cmd.py: --latest/--expect, _query_feed, _downloaded_source, gemeinsame Versionsprüfung _version_gate; Record mit Synopsis dist upgrade (<source> | --latest [--expect <version>]) …, network: yes, neuen Notes, sechs neuen Failure-Einträgen und Beispiel dist upgrade --latest --expect 8.0.0 --dry-run.
  3. tools/chemenu/commands/version_cmd.py: Record-Note und Modul-Docstring nennen dist upgrade --latest als dritten Feed-Aufrufer.
  4. tools/CONTRACT.md über docs contract --apply.
  5. instructions/upgrade-instance.md: Schritt 4 erklärt --latest und den Tarball als Offline-Alternative; Schritte 5–7 nutzen --latest --expect <version from step 2>. Schrittnummern unverändert.
  6. INSTALL.md: drei Befehle mit Netzzugriff, --latest/--expect, Echtheitshinweis, Sonderfall „erster Sprung auf ein Release, das --latest kennt“ (ohne Versionsnummer, weil der Kandidat noch 7.1.0-beta.N heißt).
  7. Tests: test_dist_upgrade.py mit lokalem http.server (Anfrage- und Authorization-Log, tempfile.tempdir umgelenkt); test_cli.py nimmt dist upgrade in die Menge der network: yes-Befehle auf.
  8. Version: version bump --minor --impact high, CHANGES-Prosa.
  9. Nachzug beim Abschluss: EVALS.md sagte, die echten urllib-Zeilen in version.py blieben ungetestet; das stimmt nicht mehr (Lauf gegen lokalen http.server), korrigiert in 94deccb.

Akzeptanzkriterien

  • Gegen einen lokalen Test-Feed lädt dist upgrade --latest das neueste Release, prüft die sha256 und führt das Upgrade durch; jede Datei stimmt per Digest mit dem Baum nach dist upgrade <tarball> überein (test_latest_yields_the_same_tree_as_the_archive_it_downloaded).
  • Falsche oder fehlende .sha256 → Exit 1, nichts geschrieben, kein wikitool-upgrade-*-Eintrag im Temp-Verzeichnis.
  • Feed nicht erreichbar → Exit 1 mit Feed-URL, nie „aktuell“.
  • Installierte Version ist die neueste → erfolgreicher No-op ohne Asset-Anfrage.
  • Tarball- oder Summen-Asset fehlt → Exit 1 ohne Download, vorhandene Assets genannt.
  • Tarball-VERSION ≠ Feed-Version → Exit 1, nichts geschrieben.
  • Feed-Version ≠ --expect → Exit 1 ohne Asset-Anfrage, beide Versionen genannt; --expect ohne --latest → Exit 1.
  • WIKITOOL_UPDATE_TOKEN erreicht keine Download-URL auf einem anderen Origin.
  • Ein Test hält den Asset-Namen in release.yml und im Code zusammen.
  • INSTALL.md führt dist upgrade --latest bei den Befehlen mit Netzzugriff; docs verify, instructions verify, pytest und CI grün.

Modelle

Entwurf, Versionsteil und E1–E10: Opus (Vorbereitung am 2026-09-29). Umsetzung, Tests, Bump, CHANGES-Prosa und Doku-Nachzug: Sonnet 5.5. Abschluss (dieser Text, docs/-/Doku-Prüfung, CI): Opus 5.5.

Version

minor, 7.1.0-beta.32; landet im 8.0.0-Kandidaten (#140 D19).

Teilpaket von #140. D17 ist entschieden (Betreiber, 2026-09-28). ## Stand **Abgeschlossen am 2026-09-29** im Kandidaten 7.1.0-beta.32 (Richtung 8.0.0, #140 D19). `dist upgrade --latest [--expect <version>]` lädt das neueste Release aus dem Feed, prüft es und spielt es über den bestehenden Upgrade-Pfad ein. Geprüft: `pytest` in `tools/` komplett grün (1579 Tests, danach `test_dist_upgrade.py` mit 64 Tests nach dem letzten Zusatztest), `docs verify` und `instructions verify` grün. CI grün auf `cf89231` (Läufe 427 und 428) und auf dem Doku-Nachzug `94deccb` (Lauf 429). Gegen den echten Gitea-Feed wurde `--latest` nicht ausgeführt: Das Ursprungs-Repo hat keinen Stamp, `dist upgrade` verweigert dort. Der erste echte Lauf ist das Update einer Instanz auf 8.0.0. ## Warum Echte Installationen beziehen ihre Updates aus dem Gitea-Feed (#140 D7). Vorher lud `dist upgrade` nichts selbst herunter („Never downloads anything“). Der Agent musste Tarball und `.sha256` vorher holen, und zwar genau mit den Shell-Konstrukten (`curl`, `sha256sum`, `tar`), die #153 aus den Anleitungen entfernt. Unter Windows hätte er dafür zusätzlich pwsh-Syntax gebraucht. `version check` kannte den Feed bereits: aus dem Stamp oder aus `WIKITOOL_UPDATE_URL`. ## Ausgangslage vor der Umsetzung - **Feed:** `version.update_url()` (Env → Stamp → `DEFAULT_UPDATE_URL`, immer `…/releases/latest`). `version._fetch_latest_object()` liefert das ganze Release-Objekt aus *einer* Anfrage. `fetch_latest_notes` begründet, warum nie eine `/releases/tags/<tag>`-URL zusammengesetzt wird. - **Feed-Antwort (live geprüft, v7.0.0):** `assets[]` mit `name` und `browser_download_url`, z. B. `chemenu-stack-7.0.0.tar.gz` und `….tar.gz.sha256`. `tag_name` trägt ein `v`, das `Version.parse` akzeptiert. Den Namen setzt `.gitea/workflows/release.yml` (`name="chemenu-stack-${VERSION}"`). - **Upgrade-Pfad:** `run_upgrade()` prüft zuerst die lokalen Vorbedingungen, dann löst `_resolved_source()` die Quelle auf (Sidecar-Prüfung, Entpacken in ein `TemporaryDirectory`). `fail()` wirft `typer.Exit`; `with`-Blöcke räumen also auch im Fehlerfall auf. ## Entscheidungen - **E1 Name:** `dist upgrade --latest`. `<source>` ist optional; genau eines von beiden ist Pflicht. Beides oder keins ergibt Exit 1 vor jeder Prüfung. - **E2 Kein `--version <v>`.** Eine beliebige Version zu holen hieße, eine Tags-URL aus der `latest`-URL zu raten (Invariante 7). Ein Downgrade wird ohnehin verweigert; eine andere Version als die neueste hat keinen Anwendungsfall. - **E3 Reihenfolge:** lokale Vorbedingungen → Feed abfragen → Feed-Version gegen `--expect` und `VERSION` prüfen, **vor jedem Download** (gleich → No-op „Already at …“, älter → Downgrade-Ablehnung, Pre-Release ohne `--pre` → Ablehnung) → Asset-Paar im Release-Objekt suchen, fehlt eines → Exit 1 vor dem ersten Download → beide in ein `TemporaryDirectory(prefix="wikitool-upgrade-")` laden → Summe prüfen → bestehender Pfad. Zusätzlich: `VERSION` im Tarball muss der Feed-Version entsprechen, sonst Exit 1 und nichts wird angewendet. Die „strikte“ Summenprüfung brauchte keinen eigenen Modus in `_verify_sha256_sidecar`: auf diesem Pfad ist die Summe immer vorhanden, weil ihr Fehlen schon bei der Asset-Suche abgelehnt wird. - **E4 Asset-Suche nach exaktem Namen** über das `assets`-Feld des schon geholten Release-Objekts; die URL kommt aus `browser_download_url`, nie zusammengesetzt. Die Fehlermeldung nennt die vorhandenen Assets und die Release-Seite. Der Name steht als `version.ARCHIVE_NAME` in Python; `test_release_asset_names_match_the_workflow` hält ihn mit `release.yml` zusammen. - **E5 Token nur an denselben Origin** (Schema, Host, Port; Default-Ports normalisiert), und dann als unredirected Header, damit ein Redirect ihn nicht mitnimmt. - **E6 Kein https-Zwang.** Die sha256 kommt vom selben Host und schützt nur vor Übertragungsfehlern; die Echtheit hängt am Vertrauen in den Feed-Host. Steht in `INSTALL.md`, in `upgrade-instance.md` Schritt 4 und im Record. - **E7 Netzcode in `version.py`:** `fetch_latest_assets`/`LatestRelease`, `token_for_asset`, `download_asset` (gestreamt, 60 s Socket-Timeout, Fehler als `VersionError` mit URL). Der Docstring von `fetch_latest` nennt drei Befehle. - **E8 `--dry-run`** lädt, prüft und klassifiziert, schreibt nichts und räumt auf. - **E9 Nicht Teil dieses Pakets:** Die `upstream merge`-Verweise im Record und in der `fail()`-Meldung von `run_upgrade` bleiben für #153; ebenso der Neuschnitt von `INSTALL.md`. - **E10 (Betreiber 2026-09-29, Variante B) `--expect <version>`,** nur mit `--latest`: meldet der Feed eine andere Version, Exit 1 vor dem Download, Meldung nennt beide Versionen und verweist auf `version notes`. Verglichen wird die geparste Version (`v`-Präfix egal, `-beta.N` zulässig). `upgrade-instance.md` übergibt die Version aus Schritt 2 in Dry-Run und echtem Lauf. Die Ablehnungstexte für lokal geänderte Dateien nennen auf diesem Pfad `dist upgrade --latest --expect <version> …` als Wiederholungszeile. ## Umgesetzt 1. `tools/chemenu/version.py`: Asset-Suche, Download, Origin-Regel, Namenskonstante, Docstring. 2. `tools/chemenu/commands/dist_cmd.py`: `--latest`/`--expect`, `_query_feed`, `_downloaded_source`, gemeinsame Versionsprüfung `_version_gate`; Record mit Synopsis `dist upgrade (<source> | --latest [--expect <version>]) …`, `network: yes`, neuen Notes, sechs neuen Failure-Einträgen und Beispiel `dist upgrade --latest --expect 8.0.0 --dry-run`. 3. `tools/chemenu/commands/version_cmd.py`: Record-Note und Modul-Docstring nennen `dist upgrade --latest` als dritten Feed-Aufrufer. 4. `tools/CONTRACT.md` über `docs contract --apply`. 5. `instructions/upgrade-instance.md`: Schritt 4 erklärt `--latest` und den Tarball als Offline-Alternative; Schritte 5–7 nutzen `--latest --expect <version from step 2>`. Schrittnummern unverändert. 6. `INSTALL.md`: drei Befehle mit Netzzugriff, `--latest`/`--expect`, Echtheitshinweis, Sonderfall „erster Sprung auf ein Release, das `--latest` kennt“ (ohne Versionsnummer, weil der Kandidat noch 7.1.0-beta.N heißt). 7. Tests: `test_dist_upgrade.py` mit lokalem `http.server` (Anfrage- und `Authorization`-Log, `tempfile.tempdir` umgelenkt); `test_cli.py` nimmt `dist upgrade` in die Menge der `network: yes`-Befehle auf. 8. Version: `version bump --minor --impact high`, CHANGES-Prosa. 9. Nachzug beim Abschluss: `EVALS.md` sagte, die echten `urllib`-Zeilen in `version.py` blieben ungetestet; das stimmt nicht mehr (Lauf gegen lokalen `http.server`), korrigiert in `94deccb`. ## Akzeptanzkriterien - [x] Gegen einen lokalen Test-Feed lädt `dist upgrade --latest` das neueste Release, prüft die sha256 und führt das Upgrade durch; jede Datei stimmt per Digest mit dem Baum nach `dist upgrade <tarball>` überein (`test_latest_yields_the_same_tree_as_the_archive_it_downloaded`). - [x] Falsche oder fehlende `.sha256` → Exit 1, nichts geschrieben, kein `wikitool-upgrade-*`-Eintrag im Temp-Verzeichnis. - [x] Feed nicht erreichbar → Exit 1 mit Feed-URL, nie „aktuell“. - [x] Installierte Version ist die neueste → erfolgreicher No-op ohne Asset-Anfrage. - [x] Tarball- oder Summen-Asset fehlt → Exit 1 ohne Download, vorhandene Assets genannt. - [x] Tarball-`VERSION` ≠ Feed-Version → Exit 1, nichts geschrieben. - [x] Feed-Version ≠ `--expect` → Exit 1 ohne Asset-Anfrage, beide Versionen genannt; `--expect` ohne `--latest` → Exit 1. - [x] `WIKITOOL_UPDATE_TOKEN` erreicht keine Download-URL auf einem anderen Origin. - [x] Ein Test hält den Asset-Namen in `release.yml` und im Code zusammen. - [x] `INSTALL.md` führt `dist upgrade --latest` bei den Befehlen mit Netzzugriff; `docs verify`, `instructions verify`, `pytest` und CI grün. ## Modelle Entwurf, Versionsteil und E1–E10: Opus (Vorbereitung am 2026-09-29). Umsetzung, Tests, Bump, CHANGES-Prosa und Doku-Nachzug: Sonnet 5.5. Abschluss (dieser Text, `docs/`-/Doku-Prüfung, CI): Opus 5.5. ## Version minor, 7.1.0-beta.32; landet im 8.0.0-Kandidaten (#140 D19).
torben added the prio/plannedsize/Marea/distributionkind/build labels 2026-09-28 16:52:36 +00:00
Author
Owner

Changelog: Zur Umsetzung vorbereitet. Neu im Body: „Befund am Baum“ (Feed-Form live gegen v7.0.0 geprüft) und die Entscheidungen E1–E9 (Name --latest, kein --version, Reihenfolge mit Versionsprüfung vor dem Download, Asset-Suche nach exaktem Namen, Token nur an denselben Origin, upstream-Verweise bleiben bei #153). E10 (Absicherung zwischen Notes und Swap) ist offen. Umfang um Dateiliste und Testplan ergänzt, dazu vier Akzeptanzkriterien: Asset fehlt, Versionsabweichung, Token-Origin, Guard-Test gegen release.yml.

**Changelog:** Zur Umsetzung vorbereitet. Neu im Body: „Befund am Baum“ (Feed-Form live gegen v7.0.0 geprüft) und die Entscheidungen E1–E9 (Name `--latest`, kein `--version`, Reihenfolge mit Versionsprüfung vor dem Download, Asset-Suche nach exaktem Namen, Token nur an denselben Origin, `upstream`-Verweise bleiben bei #153). E10 (Absicherung zwischen Notes und Swap) ist offen. Umfang um Dateiliste und Testplan ergänzt, dazu vier Akzeptanzkriterien: Asset fehlt, Versionsabweichung, Token-Origin, Guard-Test gegen `release.yml`.
Author
Owner

Changelog: E10 entschieden (Betreiber): --expect <version> statt reiner Anleitungs-Prüfung. Umfang, Failure-Einträge, Testplan und ein Akzeptanzkriterium nachgezogen; Stand: Umsetzung läuft, Halt nach dem Publish vor stack-close.

**Changelog:** E10 entschieden (Betreiber): `--expect <version>` statt reiner Anleitungs-Prüfung. Umfang, Failure-Einträge, Testplan und ein Akzeptanzkriterium nachgezogen; Stand: Umsetzung läuft, Halt nach dem Publish vor `stack-close`.
Author
Owner

Abgeschlossen: Stand auf „abgeschlossen“ mit CI-Läufen 427/428/429 gesetzt, E3-Abweichung (kein eigener strikter Summenmodus) begründet, EVALS.md-Nachzug (94deccb) und Modelle je Phase ergänzt.

Abgeschlossen: Stand auf „abgeschlossen“ mit CI-Läufen 427/428/429 gesetzt, E3-Abweichung (kein eigener strikter Summenmodus) begründet, `EVALS.md`-Nachzug (`94deccb`) und Modelle je Phase ergänzt.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: torben/chemenu#161