Version-Bump von Release entkoppeln: laufender Kandidat mit Eskalation, Pre-Release-Beta-Semantik #42

Closed
opened 2026-09-02 21:00:01 +00:00 by torben · 5 comments
Owner

Befund

Heute bekommt jeder wikitool version bump sofort eine fixierte, dauerhafte Versionsnummer, unabhängig davon, ob je ein Release dazu erscheint. Fünf Minor-Bumps hintereinander ohne Release ergeben fünf verschiedene Nummern, von denen die älteren vier nie ausgeliefert wurden — Nummern werden verbraucht, ohne dass ein Release sie eingelöst hätte. Messbar: 66 Einträge in CHANGES.md, 4.3.3 nach vier Tagen; allein die Sitzung vom 2026-09-03 hat zwei Releases für zwei Prosa-Änderungen erzeugt.

Verschärft wird das durch das Version-Gate in ci.yml: jeder Push, der Stack-Pfade berührt, muss VERSION bewegen. Nummern werden damit in Commit-Granularität verbraucht, nicht in Release-Granularität.

Nachtrag 2026-09-03, nach dem Abschluss: die Formulierung „nie ausgeliefert" oben ist ungenau, und sie ist so auch in die 4.4.0-Prosa gewandert. Unter dem alten Modell wurde jeder Bump zu einem echten, getaggten Release, weil release.yml auf jede VERSION-Bewegung feuert. Der Schaden war kein Phantom-Release, sondern dass „Release" seine Bedeutung verlor: vier echte Releases in sechs Stunden (4.3.04.3.3), zwei davon für reine Prosa. Korrigiert in 4.4.1-beta.1; siehe #47 für den Prozessfehler, durch den das unbemerkt bleiben konnte.

Entscheidungen

Betreiber, 2026-09-02 — das Modell:

  • Der Stack führt zwischen zwei Releases einen laufenden Versionskandidaten, keinen neuen pro Bump.
  • Wächst der Umfang seit dem letzten Release, eskaliert der Kandidat entlang Patch → Minor → Major, statt eine neue Nummer danebenzustellen.
  • Während dieser Zeit läuft der Baum als Pre-Release mit SemVer-Suffix -beta.n.
  • Erst ein expliziter Release-Vorgang fixiert die Nummer und beendet die Pre-Release-Phase.

Betreiber, 2026-09-03 — die vier offenen Mechanikfragen:

  1. Zustandsort: VERSION trägt den Suffix selbst (4.4.0-beta.3). Kein neues Zustandsfile. Der letzte Release ist der neueste suffixfreie ##-Eintrag in CHANGES.md; die Eskalationsstufe ist die Differenz zwischen Kandidatenbasis und letztem Release, also abgeleitet statt gespeichert. Damit behält CIs Version-Gate seinen Anker („VERSION hat sich bewegt") und version show sagt weiter, was tatsächlich installiert ist.
  2. Eskalation: max-wins pro bump-Aufruf. Der Aufrufer deklariert weiterhin --patch/--minor/--major als Urteil; version-parts.md bleibt die Autorität dafür. Der Kandidat nimmt das Maximum gegen den letzten Release. Keine Klassifikation aus Commits oder Diff — version.py verwirft das im Modul-Docstring bereits mit dem richtigen Grund: publish schreibt Content-Commits in dasselbe Repo, jeder Ingest würde als Release gelesen.
  3. Beta-Sichtbarkeit: Betas werden nie veröffentlicht. Pre-Release ist ein Zustand dieses Dev-Checkouts. release.yml feuert nur bei suffixfreier VERSION. Folge, die die Versionsstelle dieser Änderung trägt: eine ausgelieferte Instanz sieht nie ein Beta, also trifft ihr Parser nie eine VERSION-Form, die er nicht kennt.
  4. Changelog-Form: ein einziger offener Eintrag pro Kandidat. Ein Heading ## X.Y.Z-beta.n - <datum> - <titel>; jeder Bump aktualisiert Version und Datum und hängt seinen --title an eine maschinenverwaltete Liste am Kopf des Eintrags. Die Prosa darunter bleibt Autorenarbeit. version notes druckt damit genau einen Eintrag pro Release.

Betreiber, 2026-09-03 (Nachtrag) — Menschendoku: der Release-Vorgang bekommt eine DEVELOPMENT.md im Repo-Root. Heute gibt es für die Erzeuger-Seite keine Menschendoku: INSTALL.md beschreibt nur, wie man ein Release konsumiert, README.md erwähnt Releases mit keinem Wort, und der Ablauf ist nur aus einem Agenten-Skill plus einer Workflow-Datei zusammenzulesen. Die Doku fährt in diesem Issue mit statt vorher, weil sich der Ablauf hier ändert — vorher geschrieben würde sie einen Prozess beschreiben, den es nach dieser Umsetzung nicht mehr gibt.

Die Mechanik konkret

VERSION hält entweder eine Release-Version X.Y.Z (direkt nach einem Release) oder einen Kandidaten X.Y.Z-beta.n.

version bump --patch|--minor|--major --title "…":

  1. Liest den letzten Release R (neuester suffixfreier Changelog-Eintrag) und den aktuellen VERSION-Stand.
  2. Bisherige Stufe s = die Stelle, um die die Kandidatenbasis über R hinausgeht; keine, wenn VERSION == R.
  3. Neue Stufe = max(s, angeforderte Stufe) in der Ordnung patch < minor < major.
  4. Neue Basis = R.bumped(neue Stufe). n = altes n + 1, bzw. 1 bei einem frisch eröffneten Kandidaten.
  5. Schreibt VERSION, aktualisiert Heading und Titelliste des offenen Eintrags — oder legt den Eintrag an.

Randfall ohne letzten Release: eine frische Distribution hat einen Changelog ohne versionierten Eintrag (top_changes_version liefert dort heute schon None, und das ist ein gültiger Zustand). Dann gibt es kein R; die Kandidatenbasis ist VERSION.bumped(angeforderte Stufe) und die Eskalation startet bei dieser Stufe. Nicht als Fehler behandeln.

Die Eskalation ist monoton: nie automatisch zurück. Ein zurückgenommener Umfang senkt den Kandidaten nicht (siehe „Nicht in diesem Issue").

version release [--title "…"] [--dry-run] fixiert: streicht den Suffix aus VERSION und setzt das Heading auf ## X.Y.Z - <heute> - <titel>. Woher der Titel kommt: ohne --title bleibt der Titel stehen, den der letzte Bump gesetzt hat; mit --title wird er ersetzt. Das ist der Normalfall und kein Luxus — ein Kandidat hat mehrere Bumps mit je eigenem Titel gesammelt, und der Release will eine zusammenfassende Überschrift statt der zufällig letzten. Committet nicht und pusht nicht (Invariante 5) — der anschließende publish bewegt VERSION auf main und löst release.yml aus. Der nächste bump eröffnet dann einen neuen Eintrag, weil der oberste suffixfrei ist; dafür ist kein weiterer Zustand nötig.

Was das an bestehendem Code bricht

Vier Stellen, die das neue Modell still beschädigen würde. Sie sind der eigentliche Umfang dieses Issues, nicht die Suffix-Arithmetik.

# Stelle Was passiert Fix
1 .gitea/workflows/release.yml Trigger ist paths: [VERSION] auf main. Unter dem neuen Modell feuert das bei jedem Bump und veröffentlicht Betas. Guard im Schritt „Resolve the version", vor dessen API-Abfrage: enthält VERSION einen Suffix, endet der Job sauber (Skip, kein Fail). Die Reihenfolge ist nicht kosmetisch — die bestehende releases/tags/-Abfrage würde sonst für jeden Beta-Bump eine Runde gegen die API drehen.
2 docs_verify.check_breaking_change_for_boundary / check_migration_for_boundary Beide vergleichen compat_key des obersten mit dem des zweitobersten Eintrags. Zwischen 4.4.0-beta.2 und 4.4.0-beta.1 liegt keine Grenze — der Breaking-Change-Zwang wäre faktisch abgeschaltet. „Vorgänger" wird der letzte Release, nicht der vorige Eintrag. Regressionstest ist Pflicht.
3 kb_state.chain() / next_link() SemVer ordnet 4.4.0-beta.1 < 4.4.0, also fällt eine Migration mit Ziel 4.4.0 aus dem Intervall (kb_version, stack_version]. Die Instanz hat die Maschinerie und bekommt die Migration nicht angesagt. Migrationsziele gegen die Kandidatenbasis vergleichen. read_kb_version() verweigert einen Prerelease: eine Inhaltsform hat kein Beta.
4 version_cmd.bump_command, --breaking / --no-migration Der Grenzübertritt passiert jetzt in dem Bump, der nach MAJOR eskaliert, nicht mehr an einer festen Nummer. Pflichtangabe greift beim Eskalationsbump; die Zeile bleibt über spätere Bumps im offenen Eintrag stehen, ein Folgebump muss sie nicht wiederholen. version-parts.md Schritt 4 (Gespräch mit dem Betreiber vor einem Grenzübertritt) gilt ab jetzt an genau diesem Punkt.

Nicht betroffen, geprüft: das Version-Gate in ci.yml bleibt gültig, weil VERSION bei jedem Bump durch n+1 weiterhin wandert. Das ist beabsichtigt und soll so bleiben. Auch version release passiert das Gate — es prüft nur „Stack-Pfad ohne VERSION-Bewegung", nicht die Gegenrichtung.

Umsetzungsplan

Alle neun Schritte umgesetzt: version.py (Datentyp/Ordnung, Kandidatenlogik), version_cmd.py (bump umgestellt, release neu), docs_verify.py/kb_state.py/dist_cmd.py/doctor.py (alle vier Funde behoben), release.yml-Guard, Agenten-Doku (version-parts.md mit neuem Abschnitt „The candidate model" und angepassten Schritten 4–8, tools/CONTRACT.md, CHANGES.md-Kopf, docs/version-model.md), DEVELOPMENT.md neu, Version-Bump dieser Änderung selbst.

Tests

Gilt instructions/dev/testing-conventions.md. Alle Fälle aus der ursprünglichen Testliste (Ordnung inkl. numerischem Beta-Vergleich, max-wins-Eskalation, Randfall ohne letzten Release, --breaking-Persistenz über Folgebumps, beide Regressionen für Fund 2 und 3, version release mit/ohne --title, version notes für einen Kandidaten) haben einen Test in test_version_cmd.py, test_docs_verify.py, test_migrate_cmd.py und test_doctor.py. Der release.yml-Guard bleibt wie geplant ungetestet (Shell in CI).

Akzeptanzkriterien

  • Version versteht -beta.n: Parsing, totale Ordnung, base, compat_key über die Basis. Tests dafür.
  • version bump führt einen laufenden Kandidaten mit max-wins-Eskalation gegen den letzten Release; n zählt pro Bump. Tests dafür, inklusive Randfall ohne letzten Release.
  • CHANGES.md trägt genau einen offenen Eintrag pro Kandidat, mit maschinenverwalteter Titelliste in <!-- wikitool:bumps -->-Markern.
  • version release fixiert die Nummer, beendet die Pre-Release-Phase, committet nichts, und behandelt --title wie oben beschrieben. Test dafür.
  • Fund 1: release.yml überspringt eine Kandidaten-VERSION sauber, vor der API-Abfrage.
  • Fund 2: Grenzübertritts-Checks in docs verify messen gegen den letzten Release. Regressionstest.
  • Fund 3: Migrationskette und kb_version rechnen gegen die Kandidatenbasis. Regressionstest.
  • Fund 4: --breaking/--no-migration greifen am Eskalationsbump und bleiben über Folgebumps erhalten.
  • tools/CONTRACT.md: Zeile für version release in Kommandotabelle und Fehlerkontrakt.
  • instructions/dev/version-parts.md, CHANGES.md-Kopf und docs/version-model.md beschreiben die neue Semantik.
  • DEVELOPMENT.md existiert, beschreibt den Release-Ablauf unter dem neuen Modell, ist bewusst nicht in dist_cmd.ROOT_FILES (mit Begründung im Code), hat eine Zeile in AGENTS.md § File naming und einen Zeiger aus README.md.
  • Version-Bump nach der neuen Regel: MINOR, 4.3.34.4.0-beta.1version release4.4.0. Changelog-Eintrag geschrieben.
  • docs verify, instructions verify, pytest grün; CI grün inklusive dist-export-Replay.

Umsetzung und Ausrollen (Stand 2026-09-03, abgeschlossen)

Vollständig umgesetzt, gepublisht, verifiziert. Publish freigegeben durch den Betreiber (Mass-Update Gate, 21 Dateien, Token 815a67d6a9fd), Commit d29d400 auf origin/main. Verifiziert:

  • CI (ci.yml, Run #91, verify): success - volle Testsuite, docs verify, instructions verify, dist export-Replay gegen eine frische Instanz, alles grün gegen den gepushten Stand.
  • Release (release.yml, Run #92, release): success - VERSION war beim Push bereits release-fixiert (4.4.0, kein Suffix mehr), der Guard aus Fund 1 hat den Job also nicht übersprungen, sondern regulär durchlaufen lassen.
  • Release-Artefakt: v4.4.0 existiert (Release-ID 88, veröffentlicht 2026-09-03T20:22:38Z), mit Tarball und Prüfsumme.

Damit ist der komplette Ablauf einmal live durchlaufen: Bump → Prosa → Verify → version release → Publish (mit Gate-Freigabe) → CI → Release.

Nachspiel, gleicher Tag: die gesamte Prosa dieser Umsetzung entstand ohne den Modellwechsel, den stack-dev Schritt 6 dafür vorsieht — der Break wurde übersprungen. Ein nachgeholter Durchgang (4.4.1-beta.1) hat darin einen echten Faktenfehler gefunden, der sowohl im Changelog als auch in docs/version-model.md stand (siehe Nachtrag ganz oben), dazu eine veraltete 2.x-Angabe in version-parts.md und eine überflüssige Kommandotabelle in DEVELOPMENT.md. Der Prozessfehler dahinter ist #47.

Abhängigkeiten

#7 (wikitool dist upgrade) war status/blocked auf dieses Issue. Die Kopplung ist nach Fund 3 enger als ursprünglich notiert: sowohl die Drei-Wege-Klassifikation in #7 als auch die Migrationskette hängen daran, dass ein Prerelease auf seine Basis zurückgeführt wird, bevor verglichen wird. Dieses Issue ist erledigt, #7 ist entsperrt.

Nicht in diesem Issue

  • Release-Drafter / automatisierte Release-Notes aus PRs — hat seit 2026-09-03 ein eigenes Issue: #46, prio/waiting, Auslöser ist die PR-Einführung.
  • De-Eskalation eines Kandidaten. Ein zurückgenommener Umfang senkt die Stufe nicht. Kein Flag dafür — wenn es je gebraucht wird, ist es ein bewusster Eingriff und ein eigenes Issue.
  • Ausgelieferte Betas. Entschieden gegen; siehe Entscheidung 3.

Vorgeschichte

Modell entschieden in der Diskussion vom 2026-09-02, im Rahmen der Aufarbeitung von #38, #30, #28, #7, #27 als Fallouts des Entwicklungsprozesses. Siehe auch #41 (Issue-Management), das denselben Sitzungsblock eröffnet hat. Die vier Mechanikfragen und die vier Funde am bestehenden Code stammen vom 2026-09-03; die DEVELOPMENT.md-Anforderung und die Bereitschaftsprüfung vom selben Tag, nach dem Landen von 4.3.2 und 4.3.3. Umsetzung, Publish und Release ebenfalls vom 2026-09-03, im direkten Anschluss.

## Befund Heute bekommt jeder `wikitool version bump` sofort eine fixierte, dauerhafte Versionsnummer, unabhängig davon, ob je ein Release dazu erscheint. Fünf Minor-Bumps hintereinander ohne Release ergeben fünf verschiedene Nummern, von denen die älteren vier nie ausgeliefert wurden — Nummern werden verbraucht, ohne dass ein Release sie eingelöst hätte. Messbar: 66 Einträge in `CHANGES.md`, `4.3.3` nach vier Tagen; allein die Sitzung vom 2026-09-03 hat zwei Releases für zwei Prosa-Änderungen erzeugt. Verschärft wird das durch das Version-Gate in `ci.yml`: jeder Push, der Stack-Pfade berührt, muss `VERSION` bewegen. Nummern werden damit in Commit-Granularität verbraucht, nicht in Release-Granularität. > **Nachtrag 2026-09-03, nach dem Abschluss:** die Formulierung „nie ausgeliefert" oben ist ungenau, und sie ist so auch in die 4.4.0-Prosa gewandert. Unter dem alten Modell wurde **jeder** Bump zu einem echten, getaggten Release, weil `release.yml` auf jede `VERSION`-Bewegung feuert. Der Schaden war kein Phantom-Release, sondern dass „Release" seine Bedeutung verlor: vier echte Releases in sechs Stunden (`4.3.0`–`4.3.3`), zwei davon für reine Prosa. Korrigiert in 4.4.1-beta.1; siehe #47 für den Prozessfehler, durch den das unbemerkt bleiben konnte. ## Entscheidungen **Betreiber, 2026-09-02 — das Modell:** - Der Stack führt zwischen zwei Releases **einen** laufenden Versionskandidaten, keinen neuen pro Bump. - Wächst der Umfang seit dem letzten Release, eskaliert der Kandidat entlang Patch → Minor → Major, statt eine neue Nummer danebenzustellen. - Während dieser Zeit läuft der Baum als Pre-Release mit SemVer-Suffix `-beta.n`. - Erst ein expliziter Release-Vorgang fixiert die Nummer und beendet die Pre-Release-Phase. **Betreiber, 2026-09-03 — die vier offenen Mechanikfragen:** 1. **Zustandsort:** `VERSION` trägt den Suffix selbst (`4.4.0-beta.3`). Kein neues Zustandsfile. Der letzte Release ist der neueste suffixfreie `##`-Eintrag in `CHANGES.md`; die Eskalationsstufe ist die Differenz zwischen Kandidatenbasis und letztem Release, also abgeleitet statt gespeichert. Damit behält CIs Version-Gate seinen Anker („VERSION hat sich bewegt") und `version show` sagt weiter, was tatsächlich installiert ist. 2. **Eskalation:** max-wins pro `bump`-Aufruf. Der Aufrufer deklariert weiterhin `--patch/--minor/--major` als Urteil; `version-parts.md` bleibt die Autorität dafür. Der Kandidat nimmt das Maximum gegen den letzten Release. Keine Klassifikation aus Commits oder Diff — `version.py` verwirft das im Modul-Docstring bereits mit dem richtigen Grund: `publish` schreibt Content-Commits in dasselbe Repo, jeder Ingest würde als Release gelesen. 3. **Beta-Sichtbarkeit:** Betas werden **nie** veröffentlicht. Pre-Release ist ein Zustand dieses Dev-Checkouts. `release.yml` feuert nur bei suffixfreier `VERSION`. Folge, die die Versionsstelle dieser Änderung trägt: eine ausgelieferte Instanz sieht nie ein Beta, also trifft ihr Parser nie eine `VERSION`-Form, die er nicht kennt. 4. **Changelog-Form:** ein einziger offener Eintrag pro Kandidat. Ein Heading `## X.Y.Z-beta.n - <datum> - <titel>`; jeder Bump aktualisiert Version und Datum und hängt seinen `--title` an eine maschinenverwaltete Liste am Kopf des Eintrags. Die Prosa darunter bleibt Autorenarbeit. `version notes` druckt damit genau einen Eintrag pro Release. **Betreiber, 2026-09-03 (Nachtrag) — Menschendoku:** der Release-Vorgang bekommt eine **`DEVELOPMENT.md`** im Repo-Root. Heute gibt es für die *Erzeuger*-Seite keine Menschendoku: `INSTALL.md` beschreibt nur, wie man ein Release konsumiert, `README.md` erwähnt Releases mit keinem Wort, und der Ablauf ist nur aus einem Agenten-Skill plus einer Workflow-Datei zusammenzulesen. Die Doku fährt in diesem Issue mit statt vorher, weil sich der Ablauf hier ändert — vorher geschrieben würde sie einen Prozess beschreiben, den es nach dieser Umsetzung nicht mehr gibt. ## Die Mechanik konkret `VERSION` hält entweder eine Release-Version `X.Y.Z` (direkt nach einem Release) oder einen Kandidaten `X.Y.Z-beta.n`. `version bump --patch|--minor|--major --title "…"`: 1. Liest den letzten Release `R` (neuester suffixfreier Changelog-Eintrag) und den aktuellen `VERSION`-Stand. 2. Bisherige Stufe `s` = die Stelle, um die die Kandidatenbasis über `R` hinausgeht; keine, wenn `VERSION == R`. 3. Neue Stufe = `max(s, angeforderte Stufe)` in der Ordnung patch < minor < major. 4. Neue Basis = `R.bumped(neue Stufe)`. `n` = altes `n` + 1, bzw. 1 bei einem frisch eröffneten Kandidaten. 5. Schreibt `VERSION`, aktualisiert Heading und Titelliste des offenen Eintrags — oder legt den Eintrag an. **Randfall ohne letzten Release:** eine frische Distribution hat einen Changelog ohne versionierten Eintrag (`top_changes_version` liefert dort heute schon `None`, und das ist ein gültiger Zustand). Dann gibt es kein `R`; die Kandidatenbasis ist `VERSION.bumped(angeforderte Stufe)` und die Eskalation startet bei dieser Stufe. Nicht als Fehler behandeln. Die Eskalation ist **monoton**: nie automatisch zurück. Ein zurückgenommener Umfang senkt den Kandidaten nicht (siehe „Nicht in diesem Issue"). `version release [--title "…"] [--dry-run]` fixiert: streicht den Suffix aus `VERSION` und setzt das Heading auf `## X.Y.Z - <heute> - <titel>`. **Woher der Titel kommt:** ohne `--title` bleibt der Titel stehen, den der letzte Bump gesetzt hat; mit `--title` wird er ersetzt. Das ist der Normalfall und kein Luxus — ein Kandidat hat mehrere Bumps mit je eigenem Titel gesammelt, und der Release will eine zusammenfassende Überschrift statt der zufällig letzten. Committet nicht und pusht nicht (Invariante 5) — der anschließende `publish` bewegt `VERSION` auf `main` und löst `release.yml` aus. Der nächste `bump` eröffnet dann einen neuen Eintrag, weil der oberste suffixfrei ist; dafür ist kein weiterer Zustand nötig. ## Was das an bestehendem Code bricht Vier Stellen, die das neue Modell still beschädigen würde. Sie sind der eigentliche Umfang dieses Issues, nicht die Suffix-Arithmetik. | # | Stelle | Was passiert | Fix | |---|---|---|---| | 1 | `.gitea/workflows/release.yml` | Trigger ist `paths: [VERSION]` auf `main`. Unter dem neuen Modell feuert das bei **jedem** Bump und veröffentlicht Betas. | Guard im Schritt „Resolve the version", **vor** dessen API-Abfrage: enthält `VERSION` einen Suffix, endet der Job sauber (Skip, **kein** Fail). Die Reihenfolge ist nicht kosmetisch — die bestehende `releases/tags/`-Abfrage würde sonst für jeden Beta-Bump eine Runde gegen die API drehen. | | 2 | `docs_verify.check_breaking_change_for_boundary` / `check_migration_for_boundary` | Beide vergleichen `compat_key` des obersten mit dem des zweitobersten Eintrags. Zwischen `4.4.0-beta.2` und `4.4.0-beta.1` liegt keine Grenze — der Breaking-Change-Zwang wäre faktisch abgeschaltet. | „Vorgänger" wird der **letzte Release**, nicht der vorige Eintrag. Regressionstest ist Pflicht. | | 3 | `kb_state.chain()` / `next_link()` | SemVer ordnet `4.4.0-beta.1 < 4.4.0`, also fällt eine Migration mit Ziel `4.4.0` aus dem Intervall `(kb_version, stack_version]`. Die Instanz hat die Maschinerie und bekommt die Migration nicht angesagt. | Migrationsziele gegen die **Kandidatenbasis** vergleichen. `read_kb_version()` verweigert einen Prerelease: eine Inhaltsform hat kein Beta. | | 4 | `version_cmd.bump_command`, `--breaking` / `--no-migration` | Der Grenzübertritt passiert jetzt in dem Bump, der nach MAJOR eskaliert, nicht mehr an einer festen Nummer. | Pflichtangabe greift beim Eskalationsbump; die Zeile bleibt über spätere Bumps im offenen Eintrag stehen, ein Folgebump muss sie nicht wiederholen. `version-parts.md` Schritt 4 (Gespräch mit dem Betreiber vor einem Grenzübertritt) gilt ab jetzt an genau diesem Punkt. | Nicht betroffen, geprüft: das Version-Gate in `ci.yml` bleibt gültig, weil `VERSION` bei jedem Bump durch `n+1` weiterhin wandert. Das ist beabsichtigt und soll so bleiben. Auch `version release` passiert das Gate — es prüft nur „Stack-Pfad ohne VERSION-Bewegung", nicht die Gegenrichtung. ## Umsetzungsplan Alle neun Schritte umgesetzt: `version.py` (Datentyp/Ordnung, Kandidatenlogik), `version_cmd.py` (`bump` umgestellt, `release` neu), `docs_verify.py`/`kb_state.py`/`dist_cmd.py`/`doctor.py` (alle vier Funde behoben), `release.yml`-Guard, Agenten-Doku (`version-parts.md` mit neuem Abschnitt „The candidate model" und angepassten Schritten 4–8, `tools/CONTRACT.md`, `CHANGES.md`-Kopf, `docs/version-model.md`), `DEVELOPMENT.md` neu, Version-Bump dieser Änderung selbst. ## Tests Gilt `instructions/dev/testing-conventions.md`. Alle Fälle aus der ursprünglichen Testliste (Ordnung inkl. numerischem Beta-Vergleich, max-wins-Eskalation, Randfall ohne letzten Release, `--breaking`-Persistenz über Folgebumps, beide Regressionen für Fund 2 und 3, `version release` mit/ohne `--title`, `version notes` für einen Kandidaten) haben einen Test in `test_version_cmd.py`, `test_docs_verify.py`, `test_migrate_cmd.py` und `test_doctor.py`. Der `release.yml`-Guard bleibt wie geplant ungetestet (Shell in CI). ## Akzeptanzkriterien - [x] `Version` versteht `-beta.n`: Parsing, totale Ordnung, `base`, `compat_key` über die Basis. Tests dafür. - [x] `version bump` führt einen laufenden Kandidaten mit max-wins-Eskalation gegen den letzten Release; `n` zählt pro Bump. Tests dafür, inklusive Randfall ohne letzten Release. - [x] `CHANGES.md` trägt genau einen offenen Eintrag pro Kandidat, mit maschinenverwalteter Titelliste in `<!-- wikitool:bumps -->`-Markern. - [x] `version release` fixiert die Nummer, beendet die Pre-Release-Phase, committet nichts, und behandelt `--title` wie oben beschrieben. Test dafür. - [x] Fund 1: `release.yml` überspringt eine Kandidaten-`VERSION` sauber, vor der API-Abfrage. - [x] Fund 2: Grenzübertritts-Checks in `docs verify` messen gegen den letzten Release. Regressionstest. - [x] Fund 3: Migrationskette und `kb_version` rechnen gegen die Kandidatenbasis. Regressionstest. - [x] Fund 4: `--breaking`/`--no-migration` greifen am Eskalationsbump und bleiben über Folgebumps erhalten. - [x] `tools/CONTRACT.md`: Zeile für `version release` in Kommandotabelle **und** Fehlerkontrakt. - [x] `instructions/dev/version-parts.md`, `CHANGES.md`-Kopf und `docs/version-model.md` beschreiben die neue Semantik. - [x] **`DEVELOPMENT.md` existiert**, beschreibt den Release-Ablauf unter dem neuen Modell, ist bewusst **nicht** in `dist_cmd.ROOT_FILES` (mit Begründung im Code), hat eine Zeile in `AGENTS.md` § File naming und einen Zeiger aus `README.md`. - [x] Version-Bump nach der neuen Regel: MINOR, `4.3.3` → `4.4.0-beta.1` → `version release` → `4.4.0`. Changelog-Eintrag geschrieben. - [x] `docs verify`, `instructions verify`, `pytest` grün; CI grün inklusive dist-export-Replay. ## Umsetzung und Ausrollen (Stand 2026-09-03, abgeschlossen) **Vollständig umgesetzt, gepublisht, verifiziert.** Publish freigegeben durch den Betreiber (Mass-Update Gate, 21 Dateien, Token `815a67d6a9fd`), Commit `d29d400` auf `origin/main`. Verifiziert: - **CI** (`ci.yml`, Run [#91](https://gitea.nehmer.net/torben/chemenu/actions/runs/136), `verify`): `success` - volle Testsuite, `docs verify`, `instructions verify`, `dist export`-Replay gegen eine frische Instanz, alles grün gegen den gepushten Stand. - **Release** (`release.yml`, Run [#92](https://gitea.nehmer.net/torben/chemenu/actions/runs/137), `release`): `success` - `VERSION` war beim Push bereits release-fixiert (`4.4.0`, kein Suffix mehr), der Guard aus Fund 1 hat den Job also nicht übersprungen, sondern regulär durchlaufen lassen. - **Release-Artefakt:** [v4.4.0](https://gitea.nehmer.net/torben/chemenu/releases/tag/v4.4.0) existiert (Release-ID 88, veröffentlicht 2026-09-03T20:22:38Z), mit Tarball und Prüfsumme. Damit ist der komplette Ablauf einmal live durchlaufen: Bump → Prosa → Verify → `version release` → Publish (mit Gate-Freigabe) → CI → Release. **Nachspiel, gleicher Tag:** die gesamte Prosa dieser Umsetzung entstand ohne den Modellwechsel, den `stack-dev` Schritt 6 dafür vorsieht — der Break wurde übersprungen. Ein nachgeholter Durchgang (4.4.1-beta.1) hat darin einen echten Faktenfehler gefunden, der sowohl im Changelog als auch in `docs/version-model.md` stand (siehe Nachtrag ganz oben), dazu eine veraltete `2.x`-Angabe in `version-parts.md` und eine überflüssige Kommandotabelle in `DEVELOPMENT.md`. Der Prozessfehler dahinter ist #47. ## Abhängigkeiten #7 (`wikitool dist upgrade`) war `status/blocked` auf dieses Issue. Die Kopplung ist nach Fund 3 enger als ursprünglich notiert: sowohl die Drei-Wege-Klassifikation in #7 als auch die Migrationskette hängen daran, dass ein Prerelease auf seine Basis zurückgeführt wird, bevor verglichen wird. **Dieses Issue ist erledigt, #7 ist entsperrt.** ## Nicht in diesem Issue - **Release-Drafter / automatisierte Release-Notes aus PRs** — hat seit 2026-09-03 ein eigenes Issue: **#46**, `prio/waiting`, Auslöser ist die PR-Einführung. - **De-Eskalation eines Kandidaten.** Ein zurückgenommener Umfang senkt die Stufe nicht. Kein Flag dafür — wenn es je gebraucht wird, ist es ein bewusster Eingriff und ein eigenes Issue. - **Ausgelieferte Betas.** Entschieden gegen; siehe Entscheidung 3. ## Vorgeschichte Modell entschieden in der Diskussion vom 2026-09-02, im Rahmen der Aufarbeitung von #38, #30, #28, #7, #27 als Fallouts des Entwicklungsprozesses. Siehe auch #41 (Issue-Management), das denselben Sitzungsblock eröffnet hat. Die vier Mechanikfragen und die vier Funde am bestehenden Code stammen vom 2026-09-03; die `DEVELOPMENT.md`-Anforderung und die Bereitschaftsprüfung vom selben Tag, nach dem Landen von 4.3.2 und 4.3.3. Umsetzung, Publish und Release ebenfalls vom 2026-09-03, im direkten Anschluss.
torben added the area/distributionkind/decisionprio/plannedsize/L labels 2026-09-02 21:00:08 +00:00
Author
Owner

Update 2026-09-03: #39/#40 sind gelandet, ohne direkten Einfluss auf die hier offenen Mechanik-Fragen (Zustandsort, Eskalationsberechnung, Pre-Release-Vergleichslogik). Position in der Reihenfolge unverändert: nach #28/#38, vor #30/#7 - #7 hängt direkt an diesem Issue.

**Update 2026-09-03:** #39/#40 sind gelandet, ohne direkten Einfluss auf die hier offenen Mechanik-Fragen (Zustandsort, Eskalationsberechnung, Pre-Release-Vergleichslogik). Position in der Reihenfolge unverändert: nach #28/#38, vor #30/#7 - #7 hängt direkt an diesem Issue.
Author
Owner

Changelog: Body von Befund+offenen Fragen auf einen umsetzbaren Plan umgeschrieben.

Neu: alle vier offenen Mechanikfragen entschieden (VERSION trägt den Suffix, max-wins pro Bump-Aufruf, Betas werden nie veröffentlicht, ein offener Changelog-Eintrag pro Kandidat), dazu die konkrete Bump-Arithmetik, ein achtstufiger Umsetzungsplan mit bindender Reihenfolge, eine Testliste und ein Abschnitt „Nicht in diesem Issue".

Vier Funde am bestehenden Code ergänzt, die vorher in keiner Anforderung standen: release.yml würde unter dem neuen Modell bei jedem Bump feuern und Betas veröffentlichen; die beiden Grenzübertritts-Checks in docs verify vergleichen den obersten mit dem zweitobersten Eintrag und wären zwischen zwei Betas faktisch abgeschaltet; kb_state.chain() lässt eine Migration mit Ziel 4.4.0 bei installiertem 4.4.0-beta.1 aus dem Intervall fallen; --breaking/--no-migration haben unter Eskalation keinen festen Zeitpunkt mehr. Fund 2 und 3 sind Regressionstests im Akzeptanzkriterienblock.

Alte Abschnitte „Offene Punkte" und die Kurzform der Akzeptanzkriterien entfallen — ersetzt durch Entscheidungen, Plan und die neue, geschnittene Kriterienliste. Release-Drafter-Recherche ist kein Kriterium mehr, sondern explizit zurückgestellt (Trigger: PR-Einführung), noch ohne eigenes Issue.

kind/decisionkind/build: die offenen Entscheidungen sind getroffen, es fehlt nur noch Umsetzungszeit. prio/planned und size/L unverändert.

**Changelog:** Body von Befund+offenen Fragen auf einen umsetzbaren Plan umgeschrieben. Neu: alle vier offenen Mechanikfragen entschieden (VERSION trägt den Suffix, max-wins pro Bump-Aufruf, Betas werden nie veröffentlicht, ein offener Changelog-Eintrag pro Kandidat), dazu die konkrete Bump-Arithmetik, ein achtstufiger Umsetzungsplan mit bindender Reihenfolge, eine Testliste und ein Abschnitt „Nicht in diesem Issue". Vier Funde am bestehenden Code ergänzt, die vorher in keiner Anforderung standen: `release.yml` würde unter dem neuen Modell bei jedem Bump feuern und Betas veröffentlichen; die beiden Grenzübertritts-Checks in `docs verify` vergleichen den obersten mit dem zweitobersten Eintrag und wären zwischen zwei Betas faktisch abgeschaltet; `kb_state.chain()` lässt eine Migration mit Ziel `4.4.0` bei installiertem `4.4.0-beta.1` aus dem Intervall fallen; `--breaking`/`--no-migration` haben unter Eskalation keinen festen Zeitpunkt mehr. Fund 2 und 3 sind Regressionstests im Akzeptanzkriterienblock. Alte Abschnitte „Offene Punkte" und die Kurzform der Akzeptanzkriterien entfallen — ersetzt durch Entscheidungen, Plan und die neue, geschnittene Kriterienliste. Release-Drafter-Recherche ist kein Kriterium mehr, sondern explizit zurückgestellt (Trigger: PR-Einführung), noch ohne eigenes Issue. `kind/decision` → `kind/build`: die offenen Entscheidungen sind getroffen, es fehlt nur noch Umsetzungszeit. `prio/planned` und `size/L` unverändert.
torben added kind/build and removed kind/decision labels 2026-09-03 17:52:13 +00:00
Author
Owner

Changelog: DEVELOPMENT.md als Anforderung aufgenommen, Bereitschaftsprüfung durchgeführt, drei dabei gefundene Lücken geschlossen.

Neu als Plan-Schritt 8 und Akzeptanzkriterium: DEVELOPMENT.md im Repo-Root für die Erzeuger-Seite des Release-Vorgangs — mit den vier Nebenbedingungen (bewusst nicht in dist_cmd.ROOT_FILES, Begründung im Code; darf deshalb nach instructions/dev/ verlinken; Zeile in AGENTS.md § File naming; Zeiger aus README.md). Alter Schritt 8 ist jetzt 9.

Bei der Bereitschaftsprüfung aufgefallen und ergänzt:

  • Marker-Konvention war unbestimmt. „Generierte Region wie seit 4.0.0" hätte eine frische Sitzung raten lassen. Jetzt festgenagelt auf <!-- wikitool:<name> --> aus blocks.py, mit der ausdrücklichen Auflage, blocks.BLOCKS nicht zu erweitern — dessen Namen speisen xref, cite und den Lint-Check unbalanced_markers, und CHANGES.md ist keine Seite.
  • Titelherkunft bei version release fehlte. Ein Kandidat sammelt mehrere Bump-Titel; welcher im Release-Heading landet, stand nirgends. Jetzt: --title optional, ohne ihn bleibt der letzte Bump-Titel stehen.
  • Randfall ohne letzten Release fehlte. Eine frische Distribution hat keinen versionierten Changelog-Eintrag. Jetzt als Mechanik und als Test benannt.

Kleinere Präzisierungen: Guard in release.yml muss vor die API-Abfrage (sonst dreht jeder Beta-Bump eine Runde gegen die API); Ordnungstest beta.9 < beta.10 ergänzt (numerisch, nicht lexikografisch); version release passiert das CI-Version-Gate, geprüft.

Neuer Abschnitt „Bereitschaft": umsetzungsbereit, Body ist die vollständige Übergabe für eine frische Sitzung. Empfehlung zur Sitzungsführung ergänzt — Design ist erledigt, also direkt in der mechanischen Phase starten (Sonnet/high nach 4.3.3).

**Changelog:** `DEVELOPMENT.md` als Anforderung aufgenommen, Bereitschaftsprüfung durchgeführt, drei dabei gefundene Lücken geschlossen. Neu als Plan-Schritt 8 und Akzeptanzkriterium: `DEVELOPMENT.md` im Repo-Root für die Erzeuger-Seite des Release-Vorgangs — mit den vier Nebenbedingungen (bewusst **nicht** in `dist_cmd.ROOT_FILES`, Begründung im Code; darf deshalb nach `instructions/dev/` verlinken; Zeile in `AGENTS.md` § File naming; Zeiger aus `README.md`). Alter Schritt 8 ist jetzt 9. Bei der Bereitschaftsprüfung aufgefallen und ergänzt: - **Marker-Konvention war unbestimmt.** „Generierte Region wie seit 4.0.0" hätte eine frische Sitzung raten lassen. Jetzt festgenagelt auf `<!-- wikitool:<name> -->` aus `blocks.py`, mit der ausdrücklichen Auflage, `blocks.BLOCKS` **nicht** zu erweitern — dessen Namen speisen `xref`, `cite` und den Lint-Check `unbalanced_markers`, und `CHANGES.md` ist keine Seite. - **Titelherkunft bei `version release` fehlte.** Ein Kandidat sammelt mehrere Bump-Titel; welcher im Release-Heading landet, stand nirgends. Jetzt: `--title` optional, ohne ihn bleibt der letzte Bump-Titel stehen. - **Randfall ohne letzten Release fehlte.** Eine frische Distribution hat keinen versionierten Changelog-Eintrag. Jetzt als Mechanik und als Test benannt. Kleinere Präzisierungen: Guard in `release.yml` muss **vor** die API-Abfrage (sonst dreht jeder Beta-Bump eine Runde gegen die API); Ordnungstest `beta.9 < beta.10` ergänzt (numerisch, nicht lexikografisch); `version release` passiert das CI-Version-Gate, geprüft. Neuer Abschnitt „Bereitschaft": **umsetzungsbereit**, Body ist die vollständige Übergabe für eine frische Sitzung. Empfehlung zur Sitzungsführung ergänzt — Design ist erledigt, also direkt in der mechanischen Phase starten (Sonnet/high nach 4.3.3).
Author
Owner

Changelog: Umsetzung abgeschlossen, alle Akzeptanzkriterien erfüllt und angehakt.

Umgesetzt wie geplant: Version mit -beta.n, escalate() (max-wins), version bump/version release auf dem neuen Modell, alle vier Funde am Bestand behoben (inkl. Regressionstests), release.yml-Guard, DEVELOPMENT.md neu, docs/version-model.md und instructions/dev/version-parts.md erweitert, tools/CONTRACT.md ergänzt. Version-Bump dieser Änderung: 4.3.34.4.0-beta.1version release4.4.0.

Neuer Abschnitt „Umsetzung" ersetzt „Bereitschaft": Stand ist implementiert und lokal grün (pytest, docs verify, instructions verify, ein manueller dist export), aber noch nicht gepusht - publish braucht die ausdrückliche Freigabe des Betreibers. Issue bleibt deshalb offen, bis das passiert ist und CI durchgelaufen ist.

**Changelog:** Umsetzung abgeschlossen, alle Akzeptanzkriterien erfüllt und angehakt. Umgesetzt wie geplant: `Version` mit `-beta.n`, `escalate()` (max-wins), `version bump`/`version release` auf dem neuen Modell, alle vier Funde am Bestand behoben (inkl. Regressionstests), `release.yml`-Guard, `DEVELOPMENT.md` neu, `docs/version-model.md` und `instructions/dev/version-parts.md` erweitert, `tools/CONTRACT.md` ergänzt. Version-Bump dieser Änderung: `4.3.3` → `4.4.0-beta.1` → `version release` → `4.4.0`. Neuer Abschnitt „Umsetzung" ersetzt „Bereitschaft": Stand ist implementiert und lokal grün (pytest, `docs verify`, `instructions verify`, ein manueller `dist export`), aber noch nicht gepusht - `publish` braucht die ausdrückliche Freigabe des Betreibers. Issue bleibt deshalb offen, bis das passiert ist und CI durchgelaufen ist.
Author
Owner

Changelog: Publish freigegeben und durchgelaufen, CI und Release grün, Issue schließt.

CI (ci.yml Run #91) und Release (release.yml Run #92) beide success gegen Commit d29d400. v4.4.0 ist veröffentlicht. Abschnitt „Bereitschaft"/„Umsetzung" durch „Umsetzung und Ausrollen" ersetzt, mit den Run-Links und dem Release-Nachweis. #7 als entsperrt vermerkt.

**Changelog:** Publish freigegeben und durchgelaufen, CI und Release grün, Issue schließt. CI (`ci.yml` Run #91) und Release (`release.yml` Run #92) beide `success` gegen Commit `d29d400`. [v4.4.0](https://gitea.nehmer.net/torben/chemenu/releases/tag/v4.4.0) ist veröffentlicht. Abschnitt „Bereitschaft"/„Umsetzung" durch „Umsetzung und Ausrollen" ersetzt, mit den Run-Links und dem Release-Nachweis. #7 als entsperrt vermerkt.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: torben/chemenu#42