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:
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.
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.
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.
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.
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.
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.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
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
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`).
Ä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.
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.
## 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.
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.
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.
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.
**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.
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`).
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:
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.
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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Befund
git merge upstream/mainbehandelt 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..gitattributesmitmerge=ourshilft 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
mainheraus, eigeneschemenu-demo-Repo) ist zurückgestellt — widerspricht dem Ein-Repo-Wunsch des Betreibers (#28). Nicht weiterverfolgen ohne neuen Auslöser.Vorschlag A (
wikitool upstreamals 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:
tools/chemenu/ownership.py: ein Prädikat (is_stack_owned) statt dreier Literal-Listen, gelesen vondist_cmd.pyundupstream_cmd.pygleichermaßen.upstream mergerestauriert über die Vereinigungsmenge vonMERGE_HEAD- undHEAD-Baum, ein Pfad nur inHEADwird als Löschung übernommen. Regressionstest:test_upstream_deletion_of_a_contract_file_lands.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.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 in4.5.0-beta.3, je mit einem Regressionstest, der gegen die alte Fassung nachweislich fehlschlägt.shutil.rmtree) — die wörtliche Übersetzung desrm -rf kb rawaus der Prosa. Fürkb//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üreval score, gespeicherte Eval- und Lint-Berichte). In dieser Instanz standen 497 Trace-Verzeichnisse unterreports/telemetry/— ein einzigerupstream mergehä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.MERGE_HEAD(unverwandte Historien, ignorierte Datei im Weg) ist_tree_paths("MERGE_HEAD")leer, und jeder stack-eigene Pfad inHEADfällt in den Zweig „der Upstream hat ihn gelöscht" —kb/CONTRACT.md,raw/CONTRACT.mdund 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 diffzwischen Vor- und Nach-Commit und kennzeichnet Löschungen.Spezifikation (umgesetzt wie unten beschrieben)
1. Eine Quelle für das Eigentum:
tools/chemenu/ownership.pyGebaut wie spezifiziert:
CONTENT_STAGES = ("kb", "raw", "work", "reports"),is_stack_owned(relative)wahr für<stage>/CONTRACT.md(nicht rekursiv) und jeden auf.templateendenden Pfad unter einer Content-Stage,EXPORT_STUB_NAMESfürdist exports eigene Stubs.dist_cmd.pyist umgestellt:CONTRACT_ONLY_STAGESleitet sich ausownership.CONTENT_STAGESab,find_leaksfragtownership.is_stack_owned/is_export_stubstatt einer eigenen_CONTENT_ALLOWED_NAMES-Liste. Konsistenztest:test_dist_cmd_contract_only_stages_agree_with_ownership.2.
tools/wikitool upstream mergeImplementiert 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 wieverify(Fund → lauter Fehler, kein automatisches Rollback), Erfolgsbericht über den tatsächlichen Diff.3.
tools/wikitool upstream verifyImplementiert:
--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 mergeist nicht budget-exempt und steht in AGENTS.md § Tool error contract auf der Nicht-idempotent-Liste nebennew,log append,publish.upstream verifyist wiemigrate verifyvon 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 parametrisiertenis_stack_owned-Tabelle):kb/CONTRACT.md→ landet ✅kb/CONVENTIONS.md.template→ landet, lokalekb/CONVENTIONS.mdbleibt unberührt ✅kb/entities/COLLECTION.md→ landet nicht (instanzeigen seit #39) ✅raw/CONTRACT.md→ die Löschung landet ✅kb/GLOSSARY.md.template) → landet ✅work/-Runs → landen nicht ✅ (auch wenn die Instanz unterwork/gar nichts trackt)tools/→ Merge bleibt offen, exit 1, kein Commit ✅.wikitool-remotes.jsonfehlt → WARN, Merge läuft (und bleibt still, wenn die Datei da ist) ✅upstream verify --sinceauf einem von Hand verpfuschten Merge → exit 1 mit der Seite in der Liste ✅ownership/dist_cmd-Konsistenz ✅Dokumentation
tools/CONTRACT.md: Kommandotabelle und Error-Contract-Tabelle umupstream merge/upstream verifyergänzt (vondocs verifyerzwungen)tools/README.md:ownership.pyim Layout-Baum verzeichnet, keine zweite Kommandotabelleinstructions/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 nachgezogenAGENTS.md§ Tool error contract:upstream mergeauf der Nicht-idempotent-Listeinstructions/gates.md: neuer Absatz zum Mass-Update-Blindfleck bei Merge-Commitsdocs/ownership-and-templates.md: neuer Abschnitt, warum die Eigentumsgrenze ein Prädikat und keine Liste istCHANGES.md: Body geschrieben, ein Eintrag über vier Bumps desselben KandidatenVersion
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ür686c08bundd2b1719(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 + offenerwork/-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
ownership.py, Stack-Dateien (<stage>/CONTRACT.md,*.templateunter einer Content-Stage) gegen Instanz-Dateien (kb/CONVENTIONS.md,kb/*/COLLECTION.md) gegen Korpusinhaltownership.pygebaut,dist_cmd.pydarauf umgestellt, Konsistenztest grünupstream mergeimplementiert, alle vier Restbefunde durch je einen Test abgedecktupstream verifyimplementiert,SKIP_COMMAND_PATHSergänztdocs verify+instructions verify+pytestgrünversion bump --minor, Changelog-Body geschriebenVerwandt
#28 (geteilte Ursache, geschlossen), #39 und #40 (gelandet, liefern die Eigentumsgrenze), #7 (
dist upgrade— kannupstream verifykü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;verifyals 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 referenced this issue2026-09-01 21:19:07 +00:00
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 rawholt alles unter beiden Stages zurück, auch die sechs Maschinerie-Dateien, die dort wohnen:Ändert der Upstream einen davon, wirft die Prozedur das Update still weg — und die Kontrollzeile
git diff --name-only $BEFORE HEAD -- kb rawmeldet dann leer, also „nachweislich funktioniert". Die Kontrolle, die dieses Issue als wichtigsten Teil bezeichnet, bestätigt hier den Fehler.dist_cmd.pykennt die Unterscheidung schon (CONTRACT_ONLY_STAGESplus jedeskb/*/COLLECTION.md). Die Prosa-Prozedur hat sie nicht — die Drift, gegen die Vorschlag A argumentiert, ist also bereits eingetreten.Konsequenz: die Pfadmenge
kb rawist falsch, und zwar auch für ein künftigeswikitool 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→demobei jeder Stack-Änderung), nicht an Konstruktion. Einmain, 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:mainträgt keine Seiten, also kann keine ankommen — weder als Konflikt noch still.setup-instance.mdund 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 indoctor), das prüft, ob ein Update Seiten bewegt hat — mit der Pfadmenge aus derselben Quelle wiedist 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 Issueslint --fail-on-errorundsources coveragesind Korpus-Checks und haben auf einem seitenfreienmainnichts mehr zu tun; sie ziehen ins Demo-Repo um, das dann die volle Nightly braucht. Inmainbleibendoctor,docs verify,instructions verify,migrate status. Der Content-Pfad-Ausschluss inci.ymlwird fürmaingegenstandslos, sollte aber stehenbleiben —kb/CONTRACT.mdist dort bewusst ausgenommen.4. Der Preis ist mit #28 kein Preis mehr
Das Issue nennt als Kosten, dass
mainzur 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 daskb/inmainweiter als Testbett missbraucht wird.Re-Labelling:
prio/2 size/M→prio/3 size/LNach
instructions/dev/issue-tracking.mdSchritt 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 verifysamt 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.mdSchritt 4 festhalten, welche Vorschläge umgesetzt wurden — insbesondere, obupstream verifyein eigenes Kommando oder eindoctor-Check geworden ist.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/blockedgesetzt.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/blockedentfernt. Vorschlag A kann jetzt spezifiziert werden - nächster Schritt in der vereinbarten Reihenfolge, nach #28/#38/#42.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.pyals einzige Quelle des Eigentums (Prädikat statt Literal-Liste,dist_cmd.pywird darauf umgestellt),upstream mergein elf Schritten mit erklärten Fehlerpfaden,upstream verify --sinceals 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 ingates.md,upstream mergenicht budget-exempt und auf die Nicht-idempotent-Liste,upstream verifyexempt wiemigrate 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;verifywird 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/Lbleibt: die Designfragen sind zu, aberownership.pysamtdist_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.2gegen den laufenden Kandidaten4.4.1-beta.1): neue Fähigkeit, in beide Richtungen drop-in, keine Migration.Umgesetzt:
tools/chemenu/ownership.py(Eigentumsgrenze als Prädikat),wikitool upstream merge/upstream verify,dist_cmd.pydarauf umgestellt, 19 neue Tests (31 Fälle), Doku intools/CONTRACT.md,tools/README.md,AGENTS.md,instructions/gates.md,instructions/private-instance.mdnachgezogen.pytest/docs verify/instructions verify/doctorgrün. Veröffentlicht als4.5.0-beta.1(Implementierung,686c08b) und4.5.0-beta.2(nachgetragene Kombinationsprobe für das letzte Akzeptanzkriterium,d2b1719).Nachtrag nach Review-Durchgang (
4.5.0-beta.3,1b5ffea). Die Umsetzung aus4.5.0-beta.1trug zwei Fehler, die die damaligen Tests nicht berührt haben und die beide Daten vernichtet hätten:shutil.rmtreeauf die ganze Content-Stage — fürkb//raw/harmlos, für das mit diesem Issue neu aufgenommenereports/nicht: dort liegen 497 Telemetrie-Trace-Verzeichnisse, gitignored und nicht rekonstruierbar. Einupstream mergehätte sie stillschweigend gelöscht.MERGE_HEAD-Guard: hätte git das Merge nicht eröffnet (unverwandte Historien), wärenkb/CONTRACT.md,raw/CONTRACT.mdund 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.mderklärt, warum die Grenze ein Prädikat ist, undtools/CONTRACT.md/private-instance.md/gates.mdsind 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.
torben referenced this issue2026-09-04 18:57:02 +00:00
torben referenced this issue2026-09-04 19:25:03 +00:00