publish: Mass-Update-Gate zählt denselben Pfad doppelt; Meldung vermischt „kein Remote“ und „nicht erreichbar“ #159

Closed
opened 2026-09-27 08:47:15 +00:00 by torben · 5 comments
Owner

Befunde aus der Analyse in #140, gültig auf jeder Plattform. Umgesetzt im 8.0.0-Kandidaten (Bump auf 8.0.0-beta.10, Commit eadc052, README-Nachzug 61130ce; Nachprüfung beim Abschluss: 8.0.0-beta.11, Commit 03743eb). Entschieden wurde Teil 3 (D25, Betreiber, 2026-09-28; erweitert um Entscheidung A, Betreiber, 2026-09-30): publish ohne --no-push bricht vor dem Commit ab, wenn das Remote nicht erreichbar oder gar nicht konfiguriert ist.

Befund 1 – das Gate zählte einen Pfad doppelt (behoben)

collect_changes (tools/chemenu/commands/git_publish.py) las git status --porcelain -z -uall. Ein Pfad, der als gelöscht vorgemerkt ist und zugleich wieder im Arbeitsbaum liegt (git rm -r raw, danach git restore --source=HEAD -- raw/CONTRACT.md ohne --staged), steht dort zweimal (D und ??). Das Gate zählte beide; git add -A hebt sie gegeneinander auf, der Commit enthielt keinen. Die Liste zur Freigabe nannte also eine Löschung und eine Neuanlage, die nie committet wurden.

Umsetzung: collect_changes staged in einem temporären Index (Kopie des echten, oder leer, wenn keiner existiert; GIT_INDEX_FILE) und liest git diff --cached --no-renames daraus (--raw --no-abbrev -z für Status und Blob-ID, --numstat -z für die Zeilenzahlen, beides auf --paths eingeschränkt). Das temporäre Verzeichnis wird in finally entfernt.

  • Digest im --confirm-Token ist die Blob-ID des gestagten Inhalts; bei einer Löschung bleibt er leer.
  • Eine Umbenennung erscheint als alter Pfad (deleted) plus neuer Pfad (added); renamed entfällt im Scale-Line.
  • Entfernt: _numstat, _untracked_stat, _changed_files, parse_porcelain_entries, parse_porcelain_z samt Tests. describe_status liest jetzt den Status-Buchstaben von --raw.
  • Die Dateiliste der Commit-Message stammt aus derselben Liste und stimmt damit ebenfalls.
  • Invariante, die hält (getestet): ein abgelehnter publish hinterlässt echten Index und Arbeitsbaum byte-identisch, auch auf einem ungeborenen Branch ohne Index-Datei und auch, wenn die Berechnung mittendrin scheitert (dann ist auch die Temp-Kopie weg). git add schreibt Blobs in den Objektspeicher; das ist erlaubt.

Befund 2 – eine Meldung für drei Zustände (behoben)

reconcile liefert bei gescheitertem Fetch jetzt genau einen von drei Status, bestimmt nur nach dem Fehlschlag:

Status Erkennung sync publish ohne --no-push
no-remote git remote get-url <remote> scheitert Exit 0 Exit 1, vor dem Commit
remote-lacks-branch git ls-remote --exit-code → 2 Exit 0 weiter wie bisher (erster Push)
unreachable sonst Exit 0 Exit 1, vor dem Commit

Jeder Status hat seine eigene Meldung; die von unreachable nennt die URL.

Teil 3 – vor dem Commit abbrechen (umgesetzt)

publish_command ruft nach dem Reconcile publish_stop_message auf und endet bei unreachable/no-remote mit fail() (Exit 1) – vor collect_changes, also vor Gate, git add und Commit, und auch bei sauberem Baum (früher: „Nothing to commit“). Beide Meldungen nennen --no-push; die von unreachable sagt, dass der nächste publish mit erreichbarem Remote den lokalen Commit mitnimmt, die von no-remote verweist auf setup-instance.md Schritt 4. Das Publish-Remote-Gate (Exit 42, wenn .wikitool-remotes.json existiert) greift weiterhin vorher; der Retry nach einem abgelehnten Push bleibt unverändert und meldet bei inzwischen unerreichbarem Remote den ursprünglichen Push-Fehler.

Akzeptanzkriterien (alle erfüllt, je ein Test in tools/chemenu/tests/test_git_publish.py)

  • Im nachgestellten Fall zählt das Gate genau eine Datei (raw/a.md, gelöscht), kein raw/CONTRACT.md.
  • Die Pfadliste von collect_changes stimmt mit der des Commits nach dem publish überein (inkl. Umbenennung).
  • Nach Gate-Ablehnung (Exit 42) sind echter Index (Bytes) und Arbeitsbaum identisch mit vorher; ebenso ohne Index-Datei und nach scheiterndem git add.
  • Erster publish einer frischen Instanz committet und pusht weiterhin.
  • no-remote, remote-lacks-branch, unreachable: drei verschiedene Meldungen; sync endet in allen dreien mit Exit 0.
  • Nicht erreichbares Remote ohne --no-push: Exit 1, kein Commit, Index und Arbeitsbaum unverändert, auch bei sauberem Baum.
  • Ohne Remote und ohne --no-push dasselbe; die Meldung verweist auf --no-push.
  • Mit --no-push wird in beiden Fällen lokal committet; der liegengebliebene Commit geht mit dem nächsten erreichbaren publish hinaus.
  • publish- und sync-Record, tools/CONTRACT.md (regeneriert), instructions/publish-cycle.md, instructions/setup-instance.md Schritte 4 und 15, instructions/gates.md, INSTALL.md und die README-Zeile zu publish beschreiben die Ablehnung und die drei Meldungen.

Version

Major-Bump im laufenden 8.0.0-Kandidaten (8.0.0-beta.10), --breaking: „publish without --no-push now exits 1 before committing when the remote is unreachable or not configured, where it used to commit locally and fail at the push - an offline session or a local-only instance must pass --no-push“. **Migration:** bleibt „none required“, am Inhalt ändert sich nichts. Der Nachzug beim Abschluss lief als Patch (8.0.0-beta.11).

Verifikation

  • pytest in tools/: 1763 passed, 3 skipped (Umsetzung); 235 passed in test_git_publish.py, test_docs_verify.py, test_instructions*.py (Nachzug).
  • tools/wikitool docs verify, tools/wikitool instructions verify, docs toc: grün bzw. aktuell, jeweils vor beiden Publishes.
  • CI: Runs 459 und 460 (eadc052), 461 (61130ce), 462 und 463 (03743eb), alle success.
  • Abschlussprüfung auf Opus gegen den Code: Changelog-Aussagen zum alten Verhalten (alte Meldung „No remote configured, or origin could not be reached“, „Nothing to commit“ bei sauberem Baum), sync-Exit 0 und Retry-Pfad am Code von 08dde00 bzw. HEAD bestätigt.

Nicht angefasst

kb/concepts/workflows/Mass-Update Gate.md beschreibt das Gate noch mit „git status --porcelain nach dem Staging“. Das ist Korpusinhalt und läuft über den normalen Quellen-/Provenance-Weg, nicht über dieses Paket.

Verwandt: #53 (Gate-Schwelle), #149 (Trailer der Commit-Message).

Befunde aus der Analyse in #140, gültig auf jeder Plattform. Umgesetzt im 8.0.0-Kandidaten (Bump auf 8.0.0-beta.10, Commit eadc052, README-Nachzug 61130ce; Nachprüfung beim Abschluss: 8.0.0-beta.11, Commit 03743eb). Entschieden wurde Teil 3 (D25, Betreiber, 2026-09-28; erweitert um Entscheidung A, Betreiber, 2026-09-30): `publish` ohne `--no-push` bricht vor dem Commit ab, wenn das Remote nicht erreichbar oder gar nicht konfiguriert ist. ## Befund 1 – das Gate zählte einen Pfad doppelt (behoben) `collect_changes` (`tools/chemenu/commands/git_publish.py`) las `git status --porcelain -z -uall`. Ein Pfad, der als gelöscht vorgemerkt ist und zugleich wieder im Arbeitsbaum liegt (`git rm -r raw`, danach `git restore --source=HEAD -- raw/CONTRACT.md` ohne `--staged`), steht dort zweimal (`D ` und `??`). Das Gate zählte beide; `git add -A` hebt sie gegeneinander auf, der Commit enthielt keinen. Die Liste zur Freigabe nannte also eine Löschung und eine Neuanlage, die nie committet wurden. **Umsetzung:** `collect_changes` staged in einem temporären Index (Kopie des echten, oder leer, wenn keiner existiert; `GIT_INDEX_FILE`) und liest `git diff --cached --no-renames` daraus (`--raw --no-abbrev -z` für Status und Blob-ID, `--numstat -z` für die Zeilenzahlen, beides auf `--paths` eingeschränkt). Das temporäre Verzeichnis wird in `finally` entfernt. - Digest im `--confirm`-Token ist die Blob-ID des gestagten Inhalts; bei einer Löschung bleibt er leer. - Eine Umbenennung erscheint als alter Pfad (deleted) plus neuer Pfad (added); `renamed` entfällt im Scale-Line. - Entfernt: `_numstat`, `_untracked_stat`, `_changed_files`, `parse_porcelain_entries`, `parse_porcelain_z` samt Tests. `describe_status` liest jetzt den Status-Buchstaben von `--raw`. - Die Dateiliste der Commit-Message stammt aus derselben Liste und stimmt damit ebenfalls. - **Invariante, die hält (getestet):** ein abgelehnter `publish` hinterlässt echten Index und Arbeitsbaum byte-identisch, auch auf einem ungeborenen Branch ohne Index-Datei und auch, wenn die Berechnung mittendrin scheitert (dann ist auch die Temp-Kopie weg). `git add` schreibt Blobs in den Objektspeicher; das ist erlaubt. ## Befund 2 – eine Meldung für drei Zustände (behoben) `reconcile` liefert bei gescheitertem Fetch jetzt genau einen von drei Status, bestimmt nur nach dem Fehlschlag: | Status | Erkennung | `sync` | `publish` ohne `--no-push` | |---|---|---|---| | `no-remote` | `git remote get-url <remote>` scheitert | Exit 0 | Exit 1, vor dem Commit | | `remote-lacks-branch` | `git ls-remote --exit-code` → 2 | Exit 0 | weiter wie bisher (erster Push) | | `unreachable` | sonst | Exit 0 | Exit 1, vor dem Commit | Jeder Status hat seine eigene Meldung; die von `unreachable` nennt die URL. ## Teil 3 – vor dem Commit abbrechen (umgesetzt) `publish_command` ruft nach dem Reconcile `publish_stop_message` auf und endet bei `unreachable`/`no-remote` mit `fail()` (Exit 1) – vor `collect_changes`, also vor Gate, `git add` und Commit, und auch bei sauberem Baum (früher: „Nothing to commit“). Beide Meldungen nennen `--no-push`; die von `unreachable` sagt, dass der nächste `publish` mit erreichbarem Remote den lokalen Commit mitnimmt, die von `no-remote` verweist auf `setup-instance.md` Schritt 4. Das Publish-Remote-Gate (Exit 42, wenn `.wikitool-remotes.json` existiert) greift weiterhin vorher; der Retry nach einem abgelehnten Push bleibt unverändert und meldet bei inzwischen unerreichbarem Remote den ursprünglichen Push-Fehler. ## Akzeptanzkriterien (alle erfüllt, je ein Test in `tools/chemenu/tests/test_git_publish.py`) - [x] Im nachgestellten Fall zählt das Gate genau eine Datei (`raw/a.md`, gelöscht), kein `raw/CONTRACT.md`. - [x] Die Pfadliste von `collect_changes` stimmt mit der des Commits nach dem `publish` überein (inkl. Umbenennung). - [x] Nach Gate-Ablehnung (Exit 42) sind echter Index (Bytes) und Arbeitsbaum identisch mit vorher; ebenso ohne Index-Datei und nach scheiterndem `git add`. - [x] Erster `publish` einer frischen Instanz committet und pusht weiterhin. - [x] `no-remote`, `remote-lacks-branch`, `unreachable`: drei verschiedene Meldungen; `sync` endet in allen dreien mit Exit 0. - [x] Nicht erreichbares Remote ohne `--no-push`: Exit 1, kein Commit, Index und Arbeitsbaum unverändert, auch bei sauberem Baum. - [x] Ohne Remote und ohne `--no-push` dasselbe; die Meldung verweist auf `--no-push`. - [x] Mit `--no-push` wird in beiden Fällen lokal committet; der liegengebliebene Commit geht mit dem nächsten erreichbaren `publish` hinaus. - [x] `publish`- und `sync`-Record, `tools/CONTRACT.md` (regeneriert), `instructions/publish-cycle.md`, `instructions/setup-instance.md` Schritte 4 und 15, `instructions/gates.md`, `INSTALL.md` und die README-Zeile zu `publish` beschreiben die Ablehnung und die drei Meldungen. ## Version Major-Bump im laufenden 8.0.0-Kandidaten (`8.0.0-beta.10`), `--breaking`: „publish without --no-push now exits 1 before committing when the remote is unreachable or not configured, where it used to commit locally and fail at the push - an offline session or a local-only instance must pass --no-push“. `**Migration:**` bleibt „none required“, am Inhalt ändert sich nichts. Der Nachzug beim Abschluss lief als Patch (`8.0.0-beta.11`). ## Verifikation - `pytest` in `tools/`: 1763 passed, 3 skipped (Umsetzung); 235 passed in `test_git_publish.py`, `test_docs_verify.py`, `test_instructions*.py` (Nachzug). - `tools/wikitool docs verify`, `tools/wikitool instructions verify`, `docs toc`: grün bzw. aktuell, jeweils vor beiden Publishes. - CI: Runs 459 und 460 (eadc052), 461 (61130ce), 462 und 463 (03743eb), alle `success`. - Abschlussprüfung auf Opus gegen den Code: Changelog-Aussagen zum alten Verhalten (alte Meldung „No remote configured, or origin could not be reached“, „Nothing to commit“ bei sauberem Baum), `sync`-Exit 0 und Retry-Pfad am Code von 08dde00 bzw. HEAD bestätigt. ## Nicht angefasst `kb/concepts/workflows/Mass-Update Gate.md` beschreibt das Gate noch mit „`git status --porcelain` nach dem Staging“. Das ist Korpusinhalt und läuft über den normalen Quellen-/Provenance-Weg, nicht über dieses Paket. Verwandt: #53 (Gate-Schwelle), #149 (Trailer der Commit-Message).
torben added the prio/plannedsize/Sarea/workflowkind/defect labels 2026-09-27 08:47:15 +00:00
Author
Owner

Changelog:

  • Entschieden (Betreiber, 2026-09-28): D25. Teil 3 gehört damit fest zu diesem Paket: publish bricht bei unerreichbarem, konfiguriertem Remote vor dem Commit ab.
  • Version: im 8.0.0-Kandidaten, im --breaking-Text erwähnt.
  • Neu: Akzeptanzkriterium zur Doku (publish-Record, publish-cycle.md).
**Changelog:** - **Entschieden (Betreiber, 2026-09-28):** D25. Teil 3 gehört damit fest zu diesem Paket: `publish` bricht bei unerreichbarem, konfiguriertem Remote vor dem Commit ab. - **Version:** im 8.0.0-Kandidaten, im `--breaking`-Text erwähnt. - **Neu:** Akzeptanzkriterium zur Doku (`publish`-Record, `publish-cycle.md`).
torben added size/M and removed size/S labels 2026-09-30 18:30:22 +00:00
Author
Owner

Changelog: Für die Entwicklung vorbereitet (2026-09-30), Body gegen den Baum geprüft.

  • Neu: Entwurf je Teil. Befund 1: Temp-Index + diff --cached --no-renames, Digest = Blob-ID, _numstat/_untracked_stat/_changed_files/parse_porcelain_* entfallen. Befund 2: drei Status no-remote/remote-lacks-branch/unreachable. Teil 3: fail() vor collect_changes, auch bei sauberem Baum (Session-Entscheidung, begründet). Dazu eine Dateiliste und die Index-Invariante für den Temp-Index.
  • Neu: Offene Frage A (kein Remote + Push → auch vor dem Commit abbrechen?), mit Empfehlung „ja“.
  • Korrigiert: Der Body sagte, D25 stehe schon im --breaking-Text des Kandidaten. Das stimmt nicht; der fertige version bump-Aufruf steht jetzt unter „Version“.
  • Akzeptanzkriterien: nachgeschärft (Index-Invariante, erster Publish, sync Exit 0, --no-push-Pfad).
  • Relabel: size/S → size/M: Drei Teile, zwei Records, eine Instruction und ein eigener Testaufwand sind mehr als „ein klarer Schnitt“.
**Changelog:** Für die Entwicklung vorbereitet (2026-09-30), Body gegen den Baum geprüft. - **Neu:** Entwurf je Teil. Befund 1: Temp-Index + `diff --cached --no-renames`, Digest = Blob-ID, `_numstat`/`_untracked_stat`/`_changed_files`/`parse_porcelain_*` entfallen. Befund 2: drei Status `no-remote`/`remote-lacks-branch`/`unreachable`. Teil 3: `fail()` vor `collect_changes`, auch bei sauberem Baum (Session-Entscheidung, begründet). Dazu eine Dateiliste und die Index-Invariante für den Temp-Index. - **Neu:** Offene Frage A (kein Remote + Push → auch vor dem Commit abbrechen?), mit Empfehlung „ja“. - **Korrigiert:** Der Body sagte, D25 stehe schon im `--breaking`-Text des Kandidaten. Das stimmt nicht; der fertige `version bump`-Aufruf steht jetzt unter „Version“. - **Akzeptanzkriterien:** nachgeschärft (Index-Invariante, erster Publish, `sync` Exit 0, `--no-push`-Pfad). - **Relabel:** `size/S` → `size/M`: Drei Teile, zwei Records, eine Instruction und ein eigener Testaufwand sind mehr als „ein klarer Schnitt“.
Author
Owner

Changelog: Frage A entschieden (Betreiber, 2026-09-30): Auch no-remote bricht ohne --no-push vor dem Commit ab; die Session-Entscheidung zum sauberen Baum ist bestätigt.

  • „Offene Frage“ entfällt. Teil 3 deckt jetzt unreachable und no-remote ab, der Befund-2-Tabelle ist eine Spalte für das Verhalten von sync/publish hinzugefügt.
  • Neu: Hinweis, dass das Publish-Remote-Gate bei vorhandener .wikitool-remotes.json vorher greift. setup-instance.md Schritt 4 und INSTALL.md stehen in der Dateiliste, außerdem die Prüfung bestehender Tests, die ohne Remote publizieren.
  • Neu: Akzeptanzkriterium für no-remote ohne --no-push; das --no-push-Kriterium deckt jetzt beide Fälle ab.
  • Geändert: Titel und --breaking-Text des version bump-Aufrufs um „or not configured“ bzw. „local-only instance“ erweitert.
**Changelog:** Frage A entschieden (Betreiber, 2026-09-30): Auch `no-remote` bricht ohne `--no-push` vor dem Commit ab; die Session-Entscheidung zum sauberen Baum ist bestätigt. - „Offene Frage“ entfällt. Teil 3 deckt jetzt `unreachable` und `no-remote` ab, der Befund-2-Tabelle ist eine Spalte für das Verhalten von `sync`/`publish` hinzugefügt. - **Neu:** Hinweis, dass das Publish-Remote-Gate bei vorhandener `.wikitool-remotes.json` vorher greift. `setup-instance.md` Schritt 4 und `INSTALL.md` stehen in der Dateiliste, außerdem die Prüfung bestehender Tests, die ohne Remote publizieren. - **Neu:** Akzeptanzkriterium für `no-remote` ohne `--no-push`; das `--no-push`-Kriterium deckt jetzt beide Fälle ab. - **Geändert:** Titel und `--breaking`-Text des `version bump`-Aufrufs um „or not configured“ bzw. „local-only instance“ erweitert.
Author
Owner

Umgesetzt in 8.0.0-beta.10 (eadc052, README-Nachzug 61130ce), CI-Runs 459, 460, 461 grün. Body auf den Endstand gebracht: Kriterien abgehakt, Design als Entscheidung formuliert, Verifikation benannt. Offen bleibt nur die Korpusseite kb/concepts/workflows/Mass-Update Gate.md (beschreibt noch das alte Porcelain-Zählen), bewusst nicht Teil dieses Pakets.

Umgesetzt in 8.0.0-beta.10 (eadc052, README-Nachzug 61130ce), CI-Runs 459, 460, 461 grün. Body auf den Endstand gebracht: Kriterien abgehakt, Design als Entscheidung formuliert, Verifikation benannt. Offen bleibt nur die Korpusseite `kb/concepts/workflows/Mass-Update Gate.md` (beschreibt noch das alte Porcelain-Zählen), bewusst nicht Teil dieses Pakets.
Author
Owner

Nachprüfung auf Opus (8.0.0-beta.11, 03743eb, CI-Runs 462/463 grün): setup-instance.md Schritt 15 zeigte den ersten publish ohne --no-push und kündigte Exit 42 an. Eine lokale Instanz bekommt dort jetzt Exit 1, deshalb nennt der Schritt das Flag. Außerdem nachgezogen: ein Satz in gates.md (das Gate zählte angeblich „working-tree changes before publish stages them“) und der _reconcile_summary-Docstring. Body entsprechend ergänzt.

Nachprüfung auf Opus (8.0.0-beta.11, 03743eb, CI-Runs 462/463 grün): `setup-instance.md` Schritt 15 zeigte den ersten `publish` ohne `--no-push` und kündigte Exit 42 an. Eine lokale Instanz bekommt dort jetzt Exit 1, deshalb nennt der Schritt das Flag. Außerdem nachgezogen: ein Satz in `gates.md` (das Gate zählte angeblich „working-tree changes before publish stages them“) und der `_reconcile_summary`-Docstring. Body entsprechend ergänzt.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: torben/chemenu#159