Upstream-Inhalt wandert in private Instanzen: Merge-Prozedur gehört in Code, und der Korpus vielleicht aus main heraus #30

Closed
opened 2026-09-01 17:13:34 +00:00 by torben · 6 comments
Owner

Befund

git merge upstream/main behandelt einen Korpus, der sich bewegt hat, gefährlich asymmetrisch: eine geänderte, löschungs-kollidierende Seite meldet sich als Konflikt, eine neu angelegte Seite wird stillschweigend gestaged, eine beidseitig gelöschte Seite ist der einzige harmlose Fall. .gitattributes mit merge=ours hilft nicht. Die Prosa-Prozedur in instructions/private-instance.md § "Taking a stack update" schloss das, aber unvollständig — siehe die vier Restbefunde unten, die jetzt alle behoben sind.

Scope

Betrifft in erster Linie Downstream-Instanzen, nicht den eigenen Workflow des Betreibers (#41, Kontext; siehe auch #28).

Vorschlag B (Demo-Korpus aus main heraus, eigenes chemenu-demo-Repo) ist zurückgestellt — widerspricht dem Ein-Repo-Wunsch des Betreibers (#28). Nicht weiterverfolgen ohne neuen Auslöser.

Vorschlag A (wikitool upstream als eigenes Kommando) ist umgesetzt.

Restbefund (2026-09-04): was eine 1:1-Übersetzung der Prosa mitgenommen hätte

Vier Fehler, die eine wörtliche Übersetzung des Skripts in Code mitgenommen hätte — alle vier sind jetzt als eigener Test abgedeckt:

  1. Der Pfadsatz stand dreifach. Behoben durch tools/chemenu/ownership.py: ein Prädikat (is_stack_owned) statt dreier Literal-Listen, gelesen von dist_cmd.py und upstream_cmd.py gleichermaßen.
  2. Eine vom Upstream gelöschte Maschinerie-Datei wurde still ignoriert. Behoben: upstream merge restauriert über die Vereinigungsmenge von MERGE_HEAD- und HEAD-Baum, ein Pfad nur in HEAD wird als Löschung übernommen. Regressionstest: test_upstream_deletion_of_a_contract_file_lands.
  3. Ein echter Konflikt in tools//types//instructions/ endete in einer Sackgasse. Behoben: unaufgelöste Pfade werden vor dem Commit geprüft; bei einem Fund bleibt das Merge offen, exit 1, keine Sackgasse ohne Erklärung. Regressionstest: test_real_conflict_in_tools_leaves_the_merge_open.
  4. Ein neuer Maschinerie-Pfad unter einer Content-Stage erreichte die Instanz nie. Behoben durch das Prädikat statt einer Liste. Regressionstest: test_new_stack_template_under_a_content_stage_lands.

Zwei weitere Fehler, gefunden im Review nach dem ersten Publish

Beide steckten in der ersten Fassung (4.5.0-beta.1), beide hätten Daten vernichtet, keiner wurde von den damaligen Tests berührt. Behoben in 4.5.0-beta.3, je mit einem Regressionstest, der gegen die alte Fassung nachweislich fehlschlägt.

  1. Die Content-Stage wurde als Ganzes gelöscht (shutil.rmtree) — die wörtliche Übersetzung des rm -rf kb raw aus der Prosa. Für kb//raw/ harmlos, für die von diesem Issue neu aufgenommenen Stages nicht: reports/ ist bis auf seinen Contract gitignored und trägt nicht-rekonstruierbare lokale Daten (Telemetrie-Traces für eval score, gespeicherte Eval- und Lint-Berichte). In dieser Instanz standen 497 Trace-Verzeichnisse unter reports/telemetry/ — ein einziger upstream merge hätte sie stillschweigend gelöscht. Die Stage wird jetzt über die getrackten Pfade beider Bäume zurückgesetzt; ignorierte lokale Daten bleiben unberührt. Regressionstest: test_merge_keeps_ignored_local_data_under_a_content_stage.
  2. Ein Merge, den git nie eröffnet hat, hätte die Maschinerie gelöscht. Ohne MERGE_HEAD (unverwandte Historien, ignorierte Datei im Weg) ist _tree_paths("MERGE_HEAD") leer, und jeder stack-eigene Pfad in HEAD fällt in den Zweig „der Upstream hat ihn gelöscht" — kb/CONTRACT.md, raw/CONTRACT.md und alle Templates verschwinden. Es wird jetzt nach dem Merge-Aufruf geprüft, dass ein Merge offen ist, sonst Abbruch mit unberührtem Baum. Regressionstest: test_a_merge_git_refuses_to_open_deletes_nothing.

Dazu eine Ehrlichkeitskorrektur: die Erfolgsmeldung zählte die wiederhergestellten statt der geänderten Pfade (ein Ein-Datei-Merge meldete vier). Sie fragt jetzt git diff zwischen Vor- und Nach-Commit und kennzeichnet Löschungen.

Spezifikation (umgesetzt wie unten beschrieben)

1. Eine Quelle für das Eigentum: tools/chemenu/ownership.py

Gebaut wie spezifiziert: CONTENT_STAGES = ("kb", "raw", "work", "reports"), is_stack_owned(relative) wahr für <stage>/CONTRACT.md (nicht rekursiv) und jeden auf .template endenden Pfad unter einer Content-Stage, EXPORT_STUB_NAMES für dist exports eigene Stubs. dist_cmd.py ist umgestellt: CONTRACT_ONLY_STAGES leitet sich aus ownership.CONTENT_STAGES ab, find_leaks fragt ownership.is_stack_owned/is_export_stub statt einer eigenen _CONTENT_ALLOWED_NAMES-Liste. Konsistenztest: test_dist_cmd_contract_only_stages_agree_with_ownership.

2. tools/wikitool upstream merge

Implementiert mit allen elf Schritten aus der Spezifikation: Vorbedingungen (sauberer Baum, kein laufendes Merge, Remote löst auf), Publish-Remote-Gate als WARN bei fehlender .wikitool-remotes.json, Fetch, "already up to date"-Kurzschluss, offenes --no-commit --no-ff-Merge (mit Abbruch, falls git gar kein Merge eröffnet hat), Content-Stages über ihre getrackten Pfade zurück auf lokal, Stack-Pfade aus der Vereinigungsmenge beider Bäume restauriert (inklusive Löschung), Prüfung auf unaufgelöste Pfade vor dem Commit, Commit, Nachkontrolle mit derselben Logik wie verify (Fund → lauter Fehler, kein automatisches Rollback), Erfolgsbericht über den tatsächlichen Diff.

3. tools/wikitool upstream verify

Implementiert: --since <rev> [--until HEAD], dieselbe _content_leaks-Funktion wie der Merge-eigene Postcheck, damit Prüfung und Wiederherstellung nicht auseinanderlaufen können.

4. Verhältnis zu den Gates

Wie in der Tabelle entschieden: Publish-Remote-Gate unberührt (WARN statt Blockade), Mass-Update-Gate greift bei Merge-Commits strukturell nicht — jetzt als eigener Absatz in instructions/gates.md mit upstream merges eigener Nachkontrolle als der tatsächlich tragenden Sicherung. upstream merge ist nicht budget-exempt und steht in AGENTS.md § Tool error contract auf der Nicht-idempotent-Liste neben new, log append, publish. upstream verify ist wie migrate verify von der Iteration-Budget-Gate ausgenommen (run_budget.SKIP_COMMAND_PATHS) — beides nachgemessen, nicht nur behauptet.

Tests

23 Tests in tools/chemenu/tests/test_upstream_cmd.py (35 Fälle inklusive der parametrisierten is_stack_owned-Tabelle):

  • Upstream ändert eine Seite, die die Instanz gelöscht hat → landet nicht
  • Upstream legt eine neue Seite an → landet nicht
  • Upstream löscht eine Seite, die die Instanz auch gelöscht hat → nichts passiert
  • Upstream ändert kb/CONTRACT.md → landet
  • Upstream ändert kb/CONVENTIONS.md.template → landet, lokale kb/CONVENTIONS.md bleibt unberührt
  • Upstream ändert kb/entities/COLLECTION.md → landet nicht (instanzeigen seit #39)
  • Upstream löscht raw/CONTRACT.md → die Löschung landet
  • Upstream legt einen neuen Stack-Pfad an (kb/GLOSSARY.md.template) → landet
  • Upstream schickt Dateien eines offenen work/-Runs → landen nicht (auch wenn die Instanz unter work/ gar nichts trackt)
  • Konflikt in tools/ → Merge bleibt offen, exit 1, kein Commit
  • Dirty Working Tree → Refusal, Tree unberührt
  • Bereits aktuell → No-op
  • .wikitool-remotes.json fehlt → WARN, Merge läuft (und bleibt still, wenn die Datei da ist)
  • upstream verify --since auf einem von Hand verpfuschten Merge → exit 1 mit der Seite in der Liste
  • ownership/dist_cmd-Konsistenz
  • Ein Upstream-Commit, der Edit + Add + Delete + Contract-Änderung + Template-Änderung + Contract-Löschung gleichzeitig bewegt
  • Ignorierte lokale Daten unter einer Content-Stage überleben den Merge
  • Ein Merge, den git nicht eröffnet, löscht nichts
  • Die Erfolgsmeldung nennt das Geänderte, nicht das Wiederhergestellte

Dokumentation

  • tools/CONTRACT.md: Kommandotabelle und Error-Contract-Tabelle um upstream merge/upstream verify ergänzt (von docs verify erzwungen)
  • tools/README.md: ownership.py im Layout-Baum verzeichnet, keine zweite Kommandotabelle
  • instructions/private-instance.md: § "Taking a stack update" verweist auf den Befehl statt das Skript auszuschreiben; die Pfadtabelle bleibt als Erklärung; Zusage, dass ignorierte lokale Daten überleben; Decision Points nachgezogen
  • AGENTS.md § Tool error contract: upstream merge auf der Nicht-idempotent-Liste
  • instructions/gates.md: neuer Absatz zum Mass-Update-Blindfleck bei Merge-Commits
  • docs/ownership-and-templates.md: neuer Abschnitt, warum die Eigentumsgrenze ein Prädikat und keine Liste ist
  • CHANGES.md: Body geschrieben, ein Eintrag über vier Bumps desselben Kandidaten

Version

4.4.1-beta.14.5.0-beta.1 (--minor, Implementierung) → 4.5.0-beta.2 (--patch, Kombinationsprobe) → 4.5.0-beta.3 (--patch, die beiden Review-Funde). Drop-in in beide Richtungen, keine Migration.

Verifiziert

pytest (939 Tests), wikitool docs verify, wikitool instructions verify, wikitool doctor — grün vor jedem der drei Publishes. CI (Gitea Actions) grün für 686c08b und d2b1719 (Runs 141–144). Zusätzlich ein End-to-End-Lauf des echten CLI gegen eine nachgebaute private Instanz: Upstream-Commit mit Edit + Add + Delete + Contract + Template + neues Template + offener work/-Run + Contract-Löschung; danach Datei für Datei geprüft, dass Content nicht landet, Instanz-Dateien (kb/CONVENTIONS.md, kb/entities/COLLECTION.md) unberührt bleiben, Stack-Dateien landen, die Contract-Löschung übernommen wird, ignorierte lokale Daten überleben, der Baum sauber ist und der zweite Lauf ein No-op meldet. Beide Regressionstests der Review-Funde wurden gegen die alte Fassung laufen gelassen und schlagen dort fehl.

Akzeptanzkriterien

  • Vorschlag B zurückgestellt, im Issue vermerkt, nicht weiterverfolgt ohne neuen Auslöser
  • Vorschlag A spezifiziert: Eigentumsgrenze als Prädikat in ownership.py, Stack-Dateien (<stage>/CONTRACT.md, *.template unter einer Content-Stage) gegen Instanz-Dateien (kb/CONVENTIONS.md, kb/*/COLLECTION.md) gegen Korpusinhalt
  • Verhältnis zum Iteration-Budget-Gate, zum Publish-Remote-Gate und zum Mass-Update-Gate geklärt
  • ownership.py gebaut, dist_cmd.py darauf umgestellt, Konsistenztest grün
  • upstream merge implementiert, alle vier Restbefunde durch je einen Test abgedeckt
  • upstream verify implementiert, SKIP_COMMAND_PATHS ergänzt
  • Test gegen einen Upstream, der in einem Commit gleichzeitig Seiten ändert/anlegt/löscht, Contract/Template ändert und eine Contract-Datei löscht
  • Dokumentation nachgezogen, docs verify + instructions verify + pytest grün
  • version bump --minor, Changelog-Body geschrieben
  • Review-Durchgang nach dem Publish: zwei datenvernichtende Fehler gefunden, behoben, je mit Regressionstest, End-to-End gegen das echte CLI verifiziert

Verwandt

#28 (geteilte Ursache, geschlossen), #39 und #40 (gelandet, liefern die Eigentumsgrenze), #7 (dist upgrade — kann upstream verify künftig als Nachkontrolle mitnutzen)

Vorgeschichte

2.2.0/2.2.1: erste Skript-Prozedur. 2026-09-02: Scope-Klärung, Abhängigkeit zu #39, Vorschlag B verworfen. 2026-09-03: #39/#40 gelandet, Blocker entfernt. 2026-09-04: Restbefund gegen die damalige Prosa-Prozedur erhoben (vier Fehler), Vorschlag A vollständig spezifiziert, drei Zuschnittfragen entschieden (work/ im Stagesatz; Remote-Gate als WARN; verify als zweites Subkommando). 2026-09-04: umgesetzt (4.5.0-beta.1), Kombinationsprobe nachgetragen (4.5.0-beta.2), Review-Durchgang fand zwei datenvernichtende Fehler in der eigenen Umsetzung und behob sie (4.5.0-beta.3).

## Befund `git merge upstream/main` behandelt einen Korpus, der sich bewegt hat, gefährlich asymmetrisch: eine geänderte, löschungs-kollidierende Seite meldet sich als Konflikt, eine neu angelegte Seite wird **stillschweigend** gestaged, eine beidseitig gelöschte Seite ist der einzige harmlose Fall. `.gitattributes` mit `merge=ours` hilft nicht. Die Prosa-Prozedur in [instructions/private-instance.md](instructions/private-instance.md) § "Taking a stack update" schloss das, aber unvollständig — siehe die vier Restbefunde unten, die jetzt alle behoben sind. ## Scope Betrifft in erster Linie **Downstream-Instanzen**, nicht den eigenen Workflow des Betreibers (#41, Kontext; siehe auch #28). **Vorschlag B** (Demo-Korpus aus `main` heraus, eigenes `chemenu-demo`-Repo) ist zurückgestellt — widerspricht dem Ein-Repo-Wunsch des Betreibers (#28). Nicht weiterverfolgen ohne neuen Auslöser. **Vorschlag A** (`wikitool upstream` als eigenes Kommando) ist umgesetzt. ## Restbefund (2026-09-04): was eine 1:1-Übersetzung der Prosa mitgenommen hätte Vier Fehler, die eine wörtliche Übersetzung des Skripts in Code mitgenommen hätte — alle vier sind jetzt als eigener Test abgedeckt: 1. **Der Pfadsatz stand dreifach.** Behoben durch `tools/chemenu/ownership.py`: ein Prädikat (`is_stack_owned`) statt dreier Literal-Listen, gelesen von `dist_cmd.py` und `upstream_cmd.py` gleichermaßen. 2. **Eine vom Upstream *gelöschte* Maschinerie-Datei wurde still ignoriert.** Behoben: `upstream merge` restauriert über die Vereinigungsmenge von `MERGE_HEAD`- und `HEAD`-Baum, ein Pfad nur in `HEAD` wird als Löschung übernommen. Regressionstest: `test_upstream_deletion_of_a_contract_file_lands`. 3. **Ein echter Konflikt in `tools/`/`types/`/`instructions/` endete in einer Sackgasse.** Behoben: unaufgelöste Pfade werden vor dem Commit geprüft; bei einem Fund bleibt das Merge offen, exit 1, keine Sackgasse ohne Erklärung. Regressionstest: `test_real_conflict_in_tools_leaves_the_merge_open`. 4. **Ein *neuer* Maschinerie-Pfad unter einer Content-Stage erreichte die Instanz nie.** Behoben durch das Prädikat statt einer Liste. Regressionstest: `test_new_stack_template_under_a_content_stage_lands`. ## Zwei weitere Fehler, gefunden im Review nach dem ersten Publish Beide steckten in der ersten Fassung (`4.5.0-beta.1`), beide hätten Daten vernichtet, keiner wurde von den damaligen Tests berührt. Behoben in `4.5.0-beta.3`, je mit einem Regressionstest, der gegen die alte Fassung nachweislich fehlschlägt. 5. **Die Content-Stage wurde als Ganzes gelöscht** (`shutil.rmtree`) — die wörtliche Übersetzung des `rm -rf kb raw` aus der Prosa. Für `kb/`/`raw/` harmlos, für die von diesem Issue *neu* aufgenommenen Stages nicht: `reports/` ist bis auf seinen Contract gitignored und trägt nicht-rekonstruierbare lokale Daten (Telemetrie-Traces für `eval score`, gespeicherte Eval- und Lint-Berichte). In dieser Instanz standen 497 Trace-Verzeichnisse unter `reports/telemetry/` — ein einziger `upstream merge` hätte sie stillschweigend gelöscht. Die Stage wird jetzt über die **getrackten** Pfade beider Bäume zurückgesetzt; ignorierte lokale Daten bleiben unberührt. Regressionstest: `test_merge_keeps_ignored_local_data_under_a_content_stage`. 6. **Ein Merge, den git nie eröffnet hat, hätte die Maschinerie gelöscht.** Ohne `MERGE_HEAD` (unverwandte Historien, ignorierte Datei im Weg) ist `_tree_paths("MERGE_HEAD")` leer, und jeder stack-eigene Pfad in `HEAD` fällt in den Zweig „der Upstream hat ihn gelöscht" — `kb/CONTRACT.md`, `raw/CONTRACT.md` und alle Templates verschwinden. Es wird jetzt nach dem Merge-Aufruf geprüft, dass ein Merge offen ist, sonst Abbruch mit unberührtem Baum. Regressionstest: `test_a_merge_git_refuses_to_open_deletes_nothing`. Dazu eine Ehrlichkeitskorrektur: die Erfolgsmeldung zählte die *wiederhergestellten* statt der geänderten Pfade (ein Ein-Datei-Merge meldete vier). Sie fragt jetzt `git diff` zwischen Vor- und Nach-Commit und kennzeichnet Löschungen. ## Spezifikation (umgesetzt wie unten beschrieben) ### 1. Eine Quelle für das Eigentum: `tools/chemenu/ownership.py` Gebaut wie spezifiziert: `CONTENT_STAGES = ("kb", "raw", "work", "reports")`, `is_stack_owned(relative)` wahr für `<stage>/CONTRACT.md` (nicht rekursiv) und jeden auf `.template` endenden Pfad unter einer Content-Stage, `EXPORT_STUB_NAMES` für `dist export`s eigene Stubs. `dist_cmd.py` ist umgestellt: `CONTRACT_ONLY_STAGES` leitet sich aus `ownership.CONTENT_STAGES` ab, `find_leaks` fragt `ownership.is_stack_owned`/`is_export_stub` statt einer eigenen `_CONTENT_ALLOWED_NAMES`-Liste. Konsistenztest: `test_dist_cmd_contract_only_stages_agree_with_ownership`. ### 2. `tools/wikitool upstream merge` Implementiert mit allen elf Schritten aus der Spezifikation: Vorbedingungen (sauberer Baum, kein laufendes Merge, Remote löst auf), Publish-Remote-Gate als WARN bei fehlender `.wikitool-remotes.json`, Fetch, "already up to date"-Kurzschluss, offenes `--no-commit --no-ff`-Merge (mit Abbruch, falls git gar kein Merge eröffnet hat), Content-Stages über ihre getrackten Pfade zurück auf lokal, Stack-Pfade aus der Vereinigungsmenge beider Bäume restauriert (inklusive Löschung), Prüfung auf unaufgelöste Pfade vor dem Commit, Commit, Nachkontrolle mit derselben Logik wie `verify` (Fund → lauter Fehler, **kein** automatisches Rollback), Erfolgsbericht über den tatsächlichen Diff. ### 3. `tools/wikitool upstream verify` Implementiert: `--since <rev> [--until HEAD]`, dieselbe `_content_leaks`-Funktion wie der Merge-eigene Postcheck, damit Prüfung und Wiederherstellung nicht auseinanderlaufen können. ### 4. Verhältnis zu den Gates Wie in der Tabelle entschieden: Publish-Remote-Gate unberührt (WARN statt Blockade), Mass-Update-Gate greift bei Merge-Commits strukturell nicht — jetzt als eigener Absatz in [instructions/gates.md](instructions/gates.md) mit `upstream merge`s eigener Nachkontrolle als der tatsächlich tragenden Sicherung. `upstream merge` ist nicht budget-exempt und steht in AGENTS.md § Tool error contract auf der Nicht-idempotent-Liste neben `new`, `log append`, `publish`. `upstream verify` ist wie `migrate verify` von der Iteration-Budget-Gate ausgenommen (`run_budget.SKIP_COMMAND_PATHS`) — beides nachgemessen, nicht nur behauptet. ## Tests 23 Tests in `tools/chemenu/tests/test_upstream_cmd.py` (35 Fälle inklusive der parametrisierten `is_stack_owned`-Tabelle): - Upstream ändert eine Seite, die die Instanz gelöscht hat → landet nicht ✅ - Upstream legt eine neue Seite an → landet nicht ✅ - Upstream löscht eine Seite, die die Instanz auch gelöscht hat → nichts passiert ✅ - Upstream ändert `kb/CONTRACT.md` → landet ✅ - Upstream ändert `kb/CONVENTIONS.md.template` → landet, lokale `kb/CONVENTIONS.md` bleibt unberührt ✅ - Upstream ändert `kb/entities/COLLECTION.md` → landet nicht (instanzeigen seit #39) ✅ - Upstream löscht `raw/CONTRACT.md` → die Löschung landet ✅ - Upstream legt einen neuen Stack-Pfad an (`kb/GLOSSARY.md.template`) → landet ✅ - Upstream schickt Dateien eines offenen `work/`-Runs → landen nicht ✅ (auch wenn die Instanz unter `work/` gar nichts trackt) - Konflikt in `tools/` → Merge bleibt offen, exit 1, kein Commit ✅ - Dirty Working Tree → Refusal, Tree unberührt ✅ - Bereits aktuell → No-op ✅ - `.wikitool-remotes.json` fehlt → WARN, Merge läuft (und bleibt still, wenn die Datei da ist) ✅ - `upstream verify --since` auf einem von Hand verpfuschten Merge → exit 1 mit der Seite in der Liste ✅ - `ownership`/`dist_cmd`-Konsistenz ✅ - Ein Upstream-Commit, der Edit + Add + Delete + Contract-Änderung + Template-Änderung + Contract-Löschung gleichzeitig bewegt ✅ - Ignorierte lokale Daten unter einer Content-Stage überleben den Merge ✅ - Ein Merge, den git nicht eröffnet, löscht nichts ✅ - Die Erfolgsmeldung nennt das Geänderte, nicht das Wiederhergestellte ✅ ## Dokumentation - `tools/CONTRACT.md`: Kommandotabelle und Error-Contract-Tabelle um `upstream merge`/`upstream verify` ergänzt (von `docs verify` erzwungen) - `tools/README.md`: `ownership.py` im Layout-Baum verzeichnet, keine zweite Kommandotabelle - `instructions/private-instance.md`: § "Taking a stack update" verweist auf den Befehl statt das Skript auszuschreiben; die Pfadtabelle bleibt als Erklärung; Zusage, dass ignorierte lokale Daten überleben; Decision Points nachgezogen - `AGENTS.md` § Tool error contract: `upstream merge` auf der Nicht-idempotent-Liste - `instructions/gates.md`: neuer Absatz zum Mass-Update-Blindfleck bei Merge-Commits - `docs/ownership-and-templates.md`: neuer Abschnitt, warum die Eigentumsgrenze ein Prädikat und keine Liste ist - `CHANGES.md`: Body geschrieben, ein Eintrag über vier Bumps desselben Kandidaten ## Version `4.4.1-beta.1` → `4.5.0-beta.1` (`--minor`, Implementierung) → `4.5.0-beta.2` (`--patch`, Kombinationsprobe) → `4.5.0-beta.3` (`--patch`, die beiden Review-Funde). Drop-in in beide Richtungen, keine Migration. ## Verifiziert `pytest` (939 Tests), `wikitool docs verify`, `wikitool instructions verify`, `wikitool doctor` — grün vor jedem der drei Publishes. CI (Gitea Actions) grün für `686c08b` und `d2b1719` (Runs 141–144). Zusätzlich ein End-to-End-Lauf des echten CLI gegen eine nachgebaute private Instanz: Upstream-Commit mit Edit + Add + Delete + Contract + Template + neues Template + offener `work/`-Run + Contract-Löschung; danach Datei für Datei geprüft, dass Content nicht landet, Instanz-Dateien (`kb/CONVENTIONS.md`, `kb/entities/COLLECTION.md`) unberührt bleiben, Stack-Dateien landen, die Contract-Löschung übernommen wird, ignorierte lokale Daten überleben, der Baum sauber ist und der zweite Lauf ein No-op meldet. Beide Regressionstests der Review-Funde wurden gegen die alte Fassung laufen gelassen und schlagen dort fehl. ## Akzeptanzkriterien - [x] Vorschlag B zurückgestellt, im Issue vermerkt, nicht weiterverfolgt ohne neuen Auslöser - [x] Vorschlag A spezifiziert: Eigentumsgrenze als Prädikat in `ownership.py`, Stack-Dateien (`<stage>/CONTRACT.md`, `*.template` unter einer Content-Stage) gegen Instanz-Dateien (`kb/CONVENTIONS.md`, `kb/*/COLLECTION.md`) gegen Korpusinhalt - [x] Verhältnis zum Iteration-Budget-Gate, zum Publish-Remote-Gate und zum Mass-Update-Gate geklärt - [x] `ownership.py` gebaut, `dist_cmd.py` darauf umgestellt, Konsistenztest grün - [x] `upstream merge` implementiert, alle vier Restbefunde durch je einen Test abgedeckt - [x] `upstream verify` implementiert, `SKIP_COMMAND_PATHS` ergänzt - [x] Test gegen einen Upstream, der in einem Commit gleichzeitig Seiten ändert/anlegt/löscht, Contract/Template ändert *und* eine Contract-Datei löscht - [x] Dokumentation nachgezogen, `docs verify` + `instructions verify` + `pytest` grün - [x] `version bump --minor`, Changelog-Body geschrieben - [x] Review-Durchgang nach dem Publish: zwei datenvernichtende Fehler gefunden, behoben, je mit Regressionstest, End-to-End gegen das echte CLI verifiziert ## Verwandt #28 (geteilte Ursache, geschlossen), #39 und #40 (gelandet, liefern die Eigentumsgrenze), #7 (`dist upgrade` — kann `upstream verify` künftig als Nachkontrolle mitnutzen) ## Vorgeschichte 2.2.0/2.2.1: erste Skript-Prozedur. 2026-09-02: Scope-Klärung, Abhängigkeit zu #39, Vorschlag B verworfen. 2026-09-03: #39/#40 gelandet, Blocker entfernt. 2026-09-04: Restbefund gegen die damalige Prosa-Prozedur erhoben (vier Fehler), Vorschlag A vollständig spezifiziert, drei Zuschnittfragen entschieden (`work/` im Stagesatz; Remote-Gate als WARN; `verify` als zweites Subkommando). 2026-09-04: umgesetzt (`4.5.0-beta.1`), Kombinationsprobe nachgetragen (`4.5.0-beta.2`), Review-Durchgang fand zwei datenvernichtende Fehler in der eigenen Umsetzung und behob sie (`4.5.0-beta.3`).
torben added the prio/plannedsize/M labels 2026-09-01 17:13:34 +00:00
Author
Owner

Stand nach der Sitzung vom 2026-09-02

Drei Ergebnisse, eine Neubewertung, ein neues vorgelagertes Issue.

1. Die Prozedur aus 2.2.1 hat ein zweites Leck — in die Gegenrichtung

git checkout HEAD -- kb raw holt alles unter beiden Stages zurück, auch die sechs Maschinerie-Dateien, die dort wohnen:

kb/CONTRACT.md   kb/{comparisons,concepts,entities,sources}/COLLECTION.md   raw/CONTRACT.md

Ändert der Upstream einen davon, wirft die Prozedur das Update still weg — und die Kontrollzeile git diff --name-only $BEFORE HEAD -- kb raw meldet dann leer, also „nachweislich funktioniert". Die Kontrolle, die dieses Issue als wichtigsten Teil bezeichnet, bestätigt hier den Fehler.

dist_cmd.py kennt die Unterscheidung schon (CONTRACT_ONLY_STAGES plus jedes kb/*/COLLECTION.md). Die Prosa-Prozedur hat sie nicht — die Drift, gegen die Vorschlag A argumentiert, ist also bereits eingetreten.

Konsequenz: die Pfadmenge kb raw ist falsch, und zwar auch für ein künftiges wikitool upstream-Kommando. Wer sie hartkodiert, kodiert den Fehler ein zweites Mal.

2. Empfehlung: B2, plus ein stark verkleinertes A

B1 fällt raus — aus dem Argument dieses Issues selbst: die Trennung hinge an Disziplin (maindemo bei jeder Stack-Änderung), nicht an Konstruktion. Ein main, in das jemand versehentlich Korpus committet, hat keinen Gate hinter sich. Dieselbe Klasse Lösung wie die Prosa-Prozedur, eine Ebene höher.

B2 (eigenes Repo chemenu-demo), weil:

  • Der Übergang ist der harmlose Fall. Löscht der Upstream seine Demo-Seiten, sieht eine bestehende private Instanz Zeile 3 der Tabelle im Issue-Body: beide Seiten gelöscht, nichts passiert. Keine Migration bei Downstreams.
  • Danach ist das Merge inhaltsfrei von Bauart. Upstream main trägt keine Seiten, also kann keine ankommen — weder als Konflikt noch still.
  • Das Demo-Repo wird eine echte Instanz und dogfoodet setup-instance.md und den Update-Pfad dauerhaft, statt einmal in CI. Als begehbares Beispiel ist ein Repo, das als Wiki durchblätterbar ist, besser als ein Korpus im Maschinen-Repo.

A schrumpft dadurch von einem Merge-Treiber auf die Kontrolle: wikitool upstream verify --since <rev> (oder als Check in doctor), das prüft, ob ein Update Seiten bewegt hat — mit der Pfadmenge aus derselben Quelle wie dist export, nicht aus einem zweiten Satz Literale. Deutlich billiger als das volle Kommando, und es schließt Leck 1 mit.

3. nightly.yml — die offene Frage des Issues

lint --fail-on-error und sources coverage sind Korpus-Checks und haben auf einem seitenfreien main nichts mehr zu tun; sie ziehen ins Demo-Repo um, das dann die volle Nightly braucht. In main bleiben doctor, docs verify, instructions verify, migrate status. Der Content-Pfad-Ausschluss in ci.yml wird für main gegenstandslos, sollte aber stehenbleiben — kb/CONTRACT.md ist dort bewusst ausgenommen.

4. Der Preis ist mit #28 kein Preis mehr

Das Issue nennt als Kosten, dass main zur leeren Hülle wird und das begehbare Beispiel verliert. Mit #28 ist das der Zielzustand: das Fixture ersetzt den Korpus als Testfläche, das Demo-Repo als Beispiel. Umgekehrt bleibt #28 ohne B2 unvollständig, weil das kb/ in main weiter als Testbett missbraucht wird.

Re-Labelling: prio/2 size/Mprio/3 size/L

Nach instructions/dev/issue-tracking.md Schritt 3, mit benanntem Auslöser statt als höfliches Nein:

  • prio/3 — der Auslöser ist #39 abgeschlossen, dann #28 abgeschlossen. Vorher lässt sich weder die Pfadmenge sauber ziehen (Leck 1) noch B2 vollziehen, ohne dem Repo die einzige Testfläche zu nehmen. Aktuell wird auch keine Instanz geschädigt: die private Arbeitsinstanz existiert noch nicht (ENVIRONMENT.md).
  • size/L — der Zuschnitt ist gewachsen: zweites Repo, CI-Umzug, upstream verify samt Tests, plus die Instruction-Nacharbeit.

Nach Abschluss geht es weiter bei

Nirgends — #30 ist das Ende der Kette (#39#28#30). Wenn es geschlossen ist, ist die Trennung Upstream/Instanz vollständig: Eigentum deklariert (#39), Testfläche vom Demo-Korpus gelöst (#28), Auslieferung inhaltsfrei von Bauart (#30). Beim Schließen nach issue-tracking.md Schritt 4 festhalten, welche Vorschläge umgesetzt wurden — insbesondere, ob upstream verify ein eigenes Kommando oder ein doctor-Check geworden ist.

## Stand nach der Sitzung vom 2026-09-02 Drei Ergebnisse, eine Neubewertung, ein neues vorgelagertes Issue. ### 1. Die Prozedur aus 2.2.1 hat ein zweites Leck — in die Gegenrichtung `git checkout HEAD -- kb raw` holt *alles* unter beiden Stages zurück, auch die sechs Maschinerie-Dateien, die dort wohnen: ``` kb/CONTRACT.md kb/{comparisons,concepts,entities,sources}/COLLECTION.md raw/CONTRACT.md ``` Ändert der Upstream einen davon, wirft die Prozedur das Update still weg — und die Kontrollzeile `git diff --name-only $BEFORE HEAD -- kb raw` meldet dann *leer*, also „nachweislich funktioniert". Die Kontrolle, die dieses Issue als wichtigsten Teil bezeichnet, bestätigt hier den Fehler. `dist_cmd.py` kennt die Unterscheidung schon (`CONTRACT_ONLY_STAGES` plus jedes `kb/*/COLLECTION.md`). Die Prosa-Prozedur hat sie nicht — die Drift, gegen die Vorschlag A argumentiert, ist also bereits eingetreten. **Konsequenz:** die Pfadmenge `kb raw` ist falsch, und zwar auch für ein künftiges `wikitool upstream`-Kommando. Wer sie hartkodiert, kodiert den Fehler ein zweites Mal. ### 2. Empfehlung: B2, plus ein stark verkleinertes A **B1 fällt raus** — aus dem Argument dieses Issues selbst: die Trennung hinge an Disziplin (`main` → `demo` bei jeder Stack-Änderung), nicht an Konstruktion. Ein `main`, in das jemand versehentlich Korpus committet, hat keinen Gate hinter sich. Dieselbe Klasse Lösung wie die Prosa-Prozedur, eine Ebene höher. **B2** (eigenes Repo `chemenu-demo`), weil: - **Der Übergang ist der harmlose Fall.** Löscht der Upstream seine Demo-Seiten, sieht eine bestehende private Instanz Zeile 3 der Tabelle im Issue-Body: beide Seiten gelöscht, nichts passiert. Keine Migration bei Downstreams. - **Danach ist das Merge inhaltsfrei von Bauart.** Upstream `main` trägt keine Seiten, also kann keine ankommen — weder als Konflikt noch still. - **Das Demo-Repo wird eine echte Instanz** und dogfoodet `setup-instance.md` und den Update-Pfad dauerhaft, statt einmal in CI. Als begehbares Beispiel ist ein Repo, das *als Wiki* durchblätterbar ist, besser als ein Korpus im Maschinen-Repo. **A schrumpft dadurch** von einem Merge-Treiber auf die Kontrolle: `wikitool upstream verify --since <rev>` (oder als Check in `doctor`), das prüft, ob ein Update Seiten bewegt hat — mit der Pfadmenge aus derselben Quelle wie `dist export`, nicht aus einem zweiten Satz Literale. Deutlich billiger als das volle Kommando, und es schließt Leck 1 mit. ### 3. `nightly.yml` — die offene Frage des Issues `lint --fail-on-error` und `sources coverage` sind Korpus-Checks und haben auf einem seitenfreien `main` nichts mehr zu tun; sie ziehen ins Demo-Repo um, das dann die volle Nightly braucht. In `main` bleiben `doctor`, `docs verify`, `instructions verify`, `migrate status`. Der Content-Pfad-Ausschluss in `ci.yml` wird für `main` gegenstandslos, sollte aber stehenbleiben — `kb/CONTRACT.md` ist dort bewusst ausgenommen. ### 4. Der Preis ist mit #28 kein Preis mehr Das Issue nennt als Kosten, dass `main` zur leeren Hülle wird und das begehbare Beispiel verliert. Mit #28 ist das der Zielzustand: das Fixture ersetzt den Korpus als Testfläche, das Demo-Repo als Beispiel. Umgekehrt bleibt #28 ohne B2 unvollständig, weil das `kb/` in `main` weiter als Testbett missbraucht wird. ### Re-Labelling: `prio/2 size/M` → `prio/3 size/L` Nach `instructions/dev/issue-tracking.md` Schritt 3, mit benanntem Auslöser statt als höfliches Nein: - **`prio/3`** — der Auslöser ist **#39 abgeschlossen, dann #28 abgeschlossen**. Vorher lässt sich weder die Pfadmenge sauber ziehen (Leck 1) noch B2 vollziehen, ohne dem Repo die einzige Testfläche zu nehmen. Aktuell wird auch keine Instanz geschädigt: die private Arbeitsinstanz existiert noch nicht (`ENVIRONMENT.md`). - **`size/L`** — der Zuschnitt ist gewachsen: zweites Repo, CI-Umzug, `upstream verify` samt Tests, plus die Instruction-Nacharbeit. --- ## Nach Abschluss geht es weiter bei Nirgends — **#30 ist das Ende der Kette** (#39 → #28 → #30). Wenn es geschlossen ist, ist die Trennung Upstream/Instanz vollständig: Eigentum deklariert (#39), Testfläche vom Demo-Korpus gelöst (#28), Auslieferung inhaltsfrei von Bauart (#30). Beim Schließen nach `issue-tracking.md` Schritt 4 festhalten, welche Vorschläge umgesetzt wurden — insbesondere, ob `upstream verify` ein eigenes Kommando oder ein `doctor`-Check geworden ist.
torben added prio/waitingsize/L and removed prio/plannedsize/M labels 2026-09-02 11:58:49 +00:00
Author
Owner

Changelog: Scope-Klarstellung ergänzt (betrifft Downstream, nicht den eigenen Workflow). Vorschlag B zurückgestellt statt offen gegen Vorschlag A abgewogen, weil der Betreiber die Ein-Repo-Struktur für sich selbst will (#28). Abhängigkeit zu #39 (zweites Leck am selben Mechanismus) neu aufgenommen, status/blocked gesetzt.

**Changelog:** Scope-Klarstellung ergänzt (betrifft Downstream, nicht den eigenen Workflow). Vorschlag B zurückgestellt statt offen gegen Vorschlag A abgewogen, weil der Betreiber die Ein-Repo-Struktur für sich selbst will (#28). Abhängigkeit zu #39 (zweites Leck am selben Mechanismus) neu aufgenommen, `status/blocked` gesetzt.
torben added the area/workflowkind/decisionstatus/blocked labels 2026-09-02 21:17:22 +00:00
Author
Owner

Changelog: #39/#40 sind gelandet (3.0.0, 4.0.0/4.1.0). Blocker aufgelöst: das zweite Leck aus #39 ist mitgelöst, die Eigentumsgrenze zwischen Stack- und Instanz-Dateien steht jetzt fest und deklariert. status/blocked entfernt. Vorschlag A kann jetzt spezifiziert werden - nächster Schritt in der vereinbarten Reihenfolge, nach #28/#38/#42.

**Changelog:** #39/#40 sind gelandet (3.0.0, 4.0.0/4.1.0). Blocker aufgelöst: das zweite Leck aus #39 ist mitgelöst, die Eigentumsgrenze zwischen Stack- und Instanz-Dateien steht jetzt fest und deklariert. `status/blocked` entfernt. Vorschlag A kann jetzt spezifiziert werden - nächster Schritt in der vereinbarten Reihenfolge, nach #28/#38/#42.
torben removed the status/blocked label 2026-09-03 04:33:57 +00:00
torben added prio/plannedkind/build and removed prio/waitingkind/decision labels 2026-09-04 04:46:56 +00:00
Author
Owner

Changelog: Body auf den Umsetzungsstand umgeschrieben. Die Anforderungen sind implementierbar, aber die alten Akzeptanzkriterien hätten zu einer 1:1-Übersetzung der Prosa-Prozedur geführt — die trägt nach #39 noch vier Fehler (Pfadsatz dreifach hartkodiert; vom Upstream gelöschte Maschinerie-Datei wird still ignoriert; echter Konflikt in tools/ endet undiagnostiziert im offenen Merge; ein neuer Stack-Pfad unter einer Content-Stage erreicht die Instanz nie). Alle vier sind jetzt als Restbefund benannt und bekommen je einen Regressionstest.

Spezifikation ergänzt: tools/chemenu/ownership.py als einzige Quelle des Eigentums (Prädikat statt Literal-Liste, dist_cmd.py wird darauf umgestellt), upstream merge in elf Schritten mit erklärten Fehlerpfaden, upstream verify --since als zweites Subkommando aus derselben Funktion. Gate-Verhältnis geklärt: Publish-Remote unberührt (WARN bei fehlender .wikitool-remotes.json), Mass-Update greift bei Merge-Commits strukturell nicht — gehört so in gates.md, upstream merge nicht budget-exempt und auf die Nicht-idempotent-Liste, upstream verify exempt wie migrate verify.

Drei Zuschnittfragen entschieden: work/ gehört in den geschützten Stagesatz (offener Upstream-Run ist dieselbe Leck-Klasse wie eine neue Seite); Remote-Gate als WARN statt als viertes Gate; verify wird mitgebaut statt als Folge-Issue vertagt.

Re-Labelling: kind/decisionkind/build, prio/waitingprio/planned. Auslöser ist der Abschluss von #28/#38/#42 plus die jetzt getroffenen Zuschnittentscheidungen — es wartet nichts mehr, nur noch Umsetzungszeit. size/L bleibt: die Designfragen sind zu, aber ownership.py samt dist_cmd-Refactor, zwei Kommandos, fünfzehn Testfälle und fünf Dokumente sind plausibel mehr als eine Sitzung.

Version bleibt --minor (4.5.0-beta.2 gegen den laufenden Kandidaten 4.4.1-beta.1): neue Fähigkeit, in beide Richtungen drop-in, keine Migration.

**Changelog:** Body auf den Umsetzungsstand umgeschrieben. Die Anforderungen sind implementierbar, aber die alten Akzeptanzkriterien hätten zu einer 1:1-Übersetzung der Prosa-Prozedur geführt — die trägt nach #39 noch vier Fehler (Pfadsatz dreifach hartkodiert; vom Upstream gelöschte Maschinerie-Datei wird still ignoriert; echter Konflikt in `tools/` endet undiagnostiziert im offenen Merge; ein neuer Stack-Pfad unter einer Content-Stage erreicht die Instanz nie). Alle vier sind jetzt als Restbefund benannt und bekommen je einen Regressionstest. Spezifikation ergänzt: `tools/chemenu/ownership.py` als einzige Quelle des Eigentums (Prädikat statt Literal-Liste, `dist_cmd.py` wird darauf umgestellt), `upstream merge` in elf Schritten mit erklärten Fehlerpfaden, `upstream verify --since` als zweites Subkommando aus derselben Funktion. Gate-Verhältnis geklärt: Publish-Remote unberührt (WARN bei fehlender `.wikitool-remotes.json`), Mass-Update greift bei Merge-Commits strukturell nicht — gehört so in `gates.md`, `upstream merge` nicht budget-exempt und auf die Nicht-idempotent-Liste, `upstream verify` exempt wie `migrate verify`. Drei Zuschnittfragen entschieden: `work/` gehört in den geschützten Stagesatz (offener Upstream-Run ist dieselbe Leck-Klasse wie eine neue Seite); Remote-Gate als WARN statt als viertes Gate; `verify` wird mitgebaut statt als Folge-Issue vertagt. **Re-Labelling:** `kind/decision` → `kind/build`, `prio/waiting` → `prio/planned`. Auslöser ist der Abschluss von #28/#38/#42 plus die jetzt getroffenen Zuschnittentscheidungen — es wartet nichts mehr, nur noch Umsetzungszeit. `size/L` bleibt: die Designfragen sind zu, aber `ownership.py` samt `dist_cmd`-Refactor, zwei Kommandos, fünfzehn Testfälle und fünf Dokumente sind plausibel mehr als eine Sitzung. Version bleibt `--minor` (`4.5.0-beta.2` gegen den laufenden Kandidaten `4.4.1-beta.1`): neue Fähigkeit, in beide Richtungen drop-in, keine Migration.
Author
Owner

Umgesetzt: tools/chemenu/ownership.py (Eigentumsgrenze als Prädikat), wikitool upstream merge/upstream verify, dist_cmd.py darauf umgestellt, 19 neue Tests (31 Fälle), Doku in tools/CONTRACT.md, tools/README.md, AGENTS.md, instructions/gates.md, instructions/private-instance.md nachgezogen. pytest/docs verify/instructions verify/doctor grün. Veröffentlicht als 4.5.0-beta.1 (Implementierung, 686c08b) und 4.5.0-beta.2 (nachgetragene Kombinationsprobe für das letzte Akzeptanzkriterium, d2b1719).

**Umgesetzt:** `tools/chemenu/ownership.py` (Eigentumsgrenze als Prädikat), `wikitool upstream merge`/`upstream verify`, `dist_cmd.py` darauf umgestellt, 19 neue Tests (31 Fälle), Doku in `tools/CONTRACT.md`, `tools/README.md`, `AGENTS.md`, `instructions/gates.md`, `instructions/private-instance.md` nachgezogen. `pytest`/`docs verify`/`instructions verify`/`doctor` grün. Veröffentlicht als `4.5.0-beta.1` (Implementierung, `686c08b`) und `4.5.0-beta.2` (nachgetragene Kombinationsprobe für das letzte Akzeptanzkriterium, `d2b1719`).
Author
Owner

Nachtrag nach Review-Durchgang (4.5.0-beta.3, 1b5ffea). Die Umsetzung aus 4.5.0-beta.1 trug zwei Fehler, die die damaligen Tests nicht berührt haben und die beide Daten vernichtet hätten:

  1. shutil.rmtree auf die ganze Content-Stage — für kb//raw/ harmlos, für das mit diesem Issue neu aufgenommene reports/ nicht: dort liegen 497 Telemetrie-Trace-Verzeichnisse, gitignored und nicht rekonstruierbar. Ein upstream merge hätte sie stillschweigend gelöscht.
  2. Fehlender MERGE_HEAD-Guard: hätte git das Merge nicht eröffnet (unverwandte Historien), wären kb/CONTRACT.md, raw/CONTRACT.md und alle Templates als „vom Upstream gelöscht" entfernt worden.

Beide behoben, je mit einem Regressionstest, der gegen die alte Fassung nachweislich fehlschlägt (nachgemessen, nicht angenommen). Dazu: die Erfolgsmeldung nennt jetzt den tatsächlichen Diff statt der Wiederherstellungsliste, docs/ownership-and-templates.md erklärt, warum die Grenze ein Prädikat ist, und tools/CONTRACT.md/private-instance.md/gates.md sind auf das korrigierte Verhalten nachgezogen. Zusätzlich End-to-End gegen das echte CLI in einer nachgebauten privaten Instanz verifiziert — Body oben, Abschnitt „Verifiziert".

Body auf den Endstand umgeschrieben. Tests jetzt 23 (35 Fälle), Suite gesamt 939 grün.

**Nachtrag nach Review-Durchgang (`4.5.0-beta.3`, `1b5ffea`).** Die Umsetzung aus `4.5.0-beta.1` trug zwei Fehler, die die damaligen Tests nicht berührt haben und die beide Daten vernichtet hätten: 1. `shutil.rmtree` auf die ganze Content-Stage — für `kb/`/`raw/` harmlos, für das mit diesem Issue neu aufgenommene `reports/` nicht: dort liegen 497 Telemetrie-Trace-Verzeichnisse, gitignored und nicht rekonstruierbar. Ein `upstream merge` hätte sie stillschweigend gelöscht. 2. Fehlender `MERGE_HEAD`-Guard: hätte git das Merge nicht eröffnet (unverwandte Historien), wären `kb/CONTRACT.md`, `raw/CONTRACT.md` und alle Templates als „vom Upstream gelöscht" entfernt worden. Beide behoben, je mit einem Regressionstest, der gegen die alte Fassung nachweislich fehlschlägt (nachgemessen, nicht angenommen). Dazu: die Erfolgsmeldung nennt jetzt den tatsächlichen Diff statt der Wiederherstellungsliste, `docs/ownership-and-templates.md` erklärt, warum die Grenze ein Prädikat ist, und `tools/CONTRACT.md`/`private-instance.md`/`gates.md` sind auf das korrigierte Verhalten nachgezogen. Zusätzlich End-to-End gegen das echte CLI in einer nachgebauten privaten Instanz verifiziert — Body oben, Abschnitt „Verifiziert". Body auf den Endstand umgeschrieben. Tests jetzt 23 (35 Fälle), Suite gesamt 939 grün.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: torben/chemenu#30