wikitool dist upgrade: ein Update anwenden, nicht nur erkennen #7
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Ergebnis
Umgesetzt und veröffentlicht:
wikitool dist upgradewendet ein Update jetzt an, statt es nur zuerkennen. Commit
cd81ba3aufmain, Version4.5.0-beta.4(fünfter Bump auf dem laufendenKandidaten,
--minor- neue Fähigkeit, drop-in in beide Richtungen). CI-Läufe#147 und
#148 grün. Lokal verifiziert:
docs verify,instructions verify, volle Testsuite (963 grün, davon 24 neu intest_dist_upgrade.py), zusätzlich einmal gegen eine leere Maschine pertesting-conventions.mdSchritt 6.Der Rest dieses Issues ist die Spezifikation, an der entlang gebaut wurde - siehe unten, was
davon eins zu eins umgesetzt wurde und was bewusst nicht.
Worum es geht
Seit
0.1.0konnte eine Instanz erkennen, dass sie veraltet ist (wikitool version checkgegen den Release-Feed). Das Anwenden eines Updates war Handarbeit nach
INSTALL.md§„Eine Instanz aktualisieren".
wikitool dist upgrade <tarball-oder-verzeichnis>schließt dieLücke.
Abgrenzung: warum dieses Issue nach #30 noch nötig war
Zwei Update-Wege, nicht austauschbar:
upstream merge— #30, geliefertdist upgrade— dieses IssueWas beide verbindet: dieselbe Eigentumsgrenze (
chemenu.ownership).dist upgradeist jetzt dervierte Aufrufer von
ownership.is_stack_owned-artiger Logik, ohne eine eigene Liste zu führen(AGENTS.md Invariante 8) - siehe „Wie umgesetzt" unten.
Die drei Versionsfakten
VERSIONversion bump/version release.wikitool-release.jsondist export(generiert, nie von Hand).wikitool-kb.jsonmigrate done(mutierbar, Instanzzustand)Der
files-Block des zweiten wird seit der Offer-Migrationsmechanik bereits gelesen(
kb_state.divergent_files()/migrate status) -dist upgradebaut auf dieser Vorarbeitauf, statt sie zu wiederholen (siehe unten).
Wie umgesetzt
Die Schreibmenge ist genau der
files-Block der neuen.wikitool-release.json, minus wasein Export aus einer leeren Vorlage neu sät (
ownership.is_export_stub- bereits vorhanden, fürkb/log.md/.gitkeep) oder einmalig sät und danach der Instanz gehört(
ownership.is_upgrade_preserved- neu, für.wikitool-kb.jsonundCHANGES.md), plus derStamp selbst, immer neu geschrieben.
dist_cmd._write_candidates/_classify_files.Die drei Fälle plus „entfallen", für jeden Kandidatpfad gegen die alte Instanz-Summe:
unverändert → geräuschlos überschrieben; neu im Release → angelegt; lokal verändert oder
lokal gelöscht → nie still überschrieben, Lauf bricht mit vollständiger Liste ab, außer
--keep-localsagt ausdrücklich, dass sie liegen bleiben sollen (dann werden sie bei jedemweiteren Lauf erneut gemeldet); im alten Stamp, nicht mehr im neuen → gemeldet, nur mit
--pruneentfernt, und auch dann nur, wenn seit Installation unverändert.
Reihenfolge, korrigiert gegenüber der ursprünglichen (falschen) Begründung im ersten Entwurf
dieses Issues: die Migrationskette für die neuen Versionen kann nur aus dem neuen Tarball
kommen (
instructions/migrations/dort), nicht durch eine andere Abfragereihenfolge aus deralten Instanz. Vor jedem Öffnen der Quelle wird stattdessen geprüft, dass gegen die installierte
Maschinerie nichts mehr aussteht (
kb_state.chain(kb_version, VERSION.base)leer) - sonstAbbruch, bevor überhaupt etwas von der Quelle gelesen wird. Nach dem Öffnen wird die Kette für
die neue Maschinerie ermittelt (
kb_state.load_migrations()bekam dafür einen optionalendirectory-Parameter, rückwärtskompatibel - alle bestehenden Aufrufer unverändert) und nurgemeldet, nie ausgeführt.
Entschiedene Designpunkte, alle wie geplant umgesetzt: kein Download (
<source>istVerzeichnis oder
.tar.gz,version checkbleibt der einzige Netzpfad); Tarball muss genau einTop-Level-Verzeichnis enthalten (die Form von
release.yml), eine.sha256-Beidatei wirdgeprüft, fehlt sie: WARN statt Abbruch;
--keep-localstatt--overwrite-local(es gibt keinKommando, das eine lokale Änderung verwirft - das bleibt Betreiberarbeit);
--prunenur fürunveränderte entfallene Dateien; Downgrade → Abbruch, Gleichstand → No-op; Vorab-Version
(
-beta.N) → Abbruch, außer--pre; schmutziger Arbeitsbaum → Abbruch, kein Git-Repo → WARN;Kompatibilitätsgrenze → laut gemeldet, blockiert nicht; kein Eintrag in
run_budget.SKIP_COMMAND_PATHS(das Kommando schreibt).Ein Fehler, den die Tests gefangen haben, nicht das Design: die erste Implementierung prüfte
die Sperre gegen lokal geänderte Dateien vor der
--dry-run-Abfrage, sodass ein Dry-Run mitExit 1 abgebrochen wäre, statt die Klassifikation nur anzuzeigen.
--dry-runist jetzt in jedemFall Exit 0 - sein ganzer Zweck ist, genau diese Liste gefahrlos vorab zu zeigen.
Bewusst nicht umgesetzt
--from-release). Eine Instanz ohne lokalenStamp hat weiterhin keine Reparatur - sie bricht mit einem Verweis auf
upstream mergeab(für den Clone-Fall) bzw. ohne Ausweg (für den Tarball-Fall ohne History). Kein realer Fall
heute; bei Bedarf ein eigenes, kleines Issue.
dist upgradeschreibt jedeskb/<name>/COLLECTION.md.templateaus dem neuen Stamp, unabhängig davon, ob diese Instanz eine Collection dieses Namens hat -
genau wie
upstream mergees seit #30 tut. Konsistent, aber ungeprüft gegen eine Instanz miteigenem Collection-Namen; wäre ein Issue gegen den Export, keins gegen das Upgrade.
instructions/private-instance.mdunverändert gelassen - der Clone-Weg ändert sich nicht;INSTALL.mdbenennt jetzt beide Wege nebeneinander und verweist von dort aus.Akzeptanzkriterien
dist upgrade --dry-runklassifiziert jeden Pfad in die drei Fälle plus „entfallen" -test_dry_run_classifies_every_case_and_writes_nothing,test_removed_file_is_reported_and_left_alone_without_prune.test_locally_modified_file_blocks_the_upgrade_by_default,test_locally_deleted_file_blocks_the_upgrade_by_default,test_keep_local_proceeds_and_leaves_the_changed_file_untouched.files-Block des neuen Stamps minus der einmalig gesätenPfade, kein Pfad außerhalb -
test_seeded_once_paths_are_never_written_even_if_the_release_stamp_lists_them(parametrisiert über
.wikitool-kb.json,CHANGES.md,kb/log.md,raw/notes/.gitkeep).geschrieben ist -
test_outstanding_local_migration_blocks_before_touching_the_source(dieQuelle wird auf einen nicht existierenden Pfad gesetzt, um das zu erzwingen).
vor dem Schreiben fest, wird gemeldet, nicht ausgeführt -
test_migration_chain_is_reported_but_never_executed.genau ein Top-Level-Verzeichnis - je ein Test:
test_missing_local_stamp_blocks,test_missing_kb_version_blocks,test_prerelease_source_is_refused_without_pre_flag,test_pre_flag_allows_a_prerelease_source,test_downgrade_is_refused,test_equal_version_is_a_noop,test_dirty_working_tree_blocks,test_tarball_with_more_than_one_top_level_entry_is_refused,test_source_that_does_not_exist_is_refused,test_tarball_sha256_sidecar_mismatch_is_refused._write_candidatesliest ausschließlichownership.is_export_stub/ownership.is_upgrade_preserved.tools/CONTRACT.mds Kommandotabelle und in der Fehlerkontrakt-Tabelle - beideergänzt,
docs verifygrün.INSTALL.md§ „Eine Instanz aktualisieren" auf den neuen Weg umgestellt, manuelle Prozedurbleibt als Rückfallebene benannt (nicht mehr ausgeschrieben - der
files-Block macht sieredundant, ein Verweis auf
tools/CONTRACT.mds Zeile genügt).instructions/private-instance.mdbewusst unberührt gelassen.4.5.0-beta.4).Relevanter Code
tools/chemenu/commands/dist_cmd.py—upgrade_command/run_upgrade,FileClassification,_write_candidates,_classify_files,_resolved_source,_extract_single_top_level_dir,_verify_sha256_sidecar,_git_working_tree_status,_report_plan.tools/chemenu/ownership.py—UPGRADE_PRESERVED_PATHS,is_upgrade_preserved.tools/chemenu/kb_state.py—load_migrations(directory=None),compare_against_stamp,UNCHANGED/MODIFIED/DELETED,divergent_files(jetzt eine Hülle darum).tools/chemenu/tests/test_dist_upgrade.py— 24 Tests.tools/CONTRACT.md,INSTALL.md§ „Eine Instanz aktualisieren",CHANGES.md.Nachtrag 2026-09-01 — zwei Annahmen im Text gelten nicht mehr
Der Fallstrick „Ursprungs-Repo ist privat" entfällt. Das Repo wird öffentlich geschaltet. Damit verschwindet die Begründung,
WIKITOOL_UPDATE_TOKENsei Pflicht, und der beschriebene Fall „ein fehlendes Release und ein fehlender Zugriff sehen identisch aus" tritt für den Normalnutzer nicht mehr auf. Er bleibt richtig für private Forks, ist dort aber Sonderfall statt Regel.INSTALL.mdwird beim Umschalten entsprechend nachgezogen.Pfade und Repo-Name sind vom Rename überholt (2.0.0, Issue #3):
tools/wiki_tools/…heißt jetzttools/chemenu/…, undtorben/llm-wiki-test1isttorben/chemenu. Betrifft im Text die Codeverweise aufdist_cmd.py,version.py,kb_state.pysowie die Release-URL.Die Dringlichkeit hat sich verschoben, die Berechtigung nicht. Die private Arbeitsinstanz wird als Clone mit
upstream-Remote angelegt, nicht als Tarball-Empfänger — Stack-Updates kommen dort pergit merge upstream/main, also mit echtem Drei-Wege-Merge stattcp -r. Damit istdist upgradefür diese Instanz nicht der kritische Pfad. Für jede fremde Instanz ohne gemeinsame Git-History bleibt es genau das, was dieses Issue beschreibt, und die Drei-Wege-Klassifikation über diefiles-Summen in.wikitool-release.jsonist weiterhin der richtige Entwurf.Update 2026-09-03: #39/#40 sind gelandet. Die Ownership-Ambiguität, was als "Stack-Datei" vs. "Instanz-Datei" gilt, ist damit geklärt (siehe #39s Tabelle, #40s
STACK_REQUIRED_TYPES) - die Drei-Wege-Klassifikation dieses Issues kann sich darauf stützen. Verbleibender Blocker ist ausschließlich #42 (Pre-Release-Vergleichslogik). Reihenfolge unverändert: #7 bleibt der letzte Schritt, nach #28, #38, #42, #30.Changelog: Body vollständig neu geschrieben — Anforderungen geprüft und ergänzt, die beiden Nachträge oben sind darin aufgegangen und nur noch Historie.
Korrigiert: „den
files-Block liest bisher niemand" stimmt nicht mehr (kb_state.divergent_files()/migrate status); die Reihenfolgen-Begründung war falsch hergeleitet — die Migrationsdokumente liegen im neuen Tarball, die alte Instanz kann die Kette gar nicht kennen, also ist die Vorbedingung „nichts offen" das, was vorher aus der Instanz kommen muss, nicht die Kette; Pfade/Repo/Version auftools/chemenu,torben/chemenu,4.5.0-beta.3gezogen; Privat-Repo-Fallstrick auf den Fork-Sonderfall reduziert.Neu: Abgrenzung gegen
upstream merge(#30) als Tabelle — zwei Update-Wege, nicht austauschbar. Die tragende Regel „Schreibmenge =files-Block des neuen Stamps plus Stamp selbst, minus einmalig gesäter Pfade", inkl. der bisher nirgends benannten Ausnahmen.wikitool-kb.jsonundCHANGES.md. 14 entschiedene Designpunkte mit Begründung (kein Download,--keep-localstatt--overwrite-local,--prune,--pre, Downgrade-Abbruch, kein Budget-Skip — letzteres, weilSKIP_COMMAND_PATHS(command, subcommand)-Tupel hält und Flags gar nicht unterscheiden kann). Abschnitt „Offene Konstruktionsarbeit" (Signaturänderungen anload_migrations/divergent_files, Satz inownership.py) und „Bewusst offen gelassen" (Collection-Templates fremder Namen — Verhalten folgtupstream merge).Akzeptanzkriterien von 7 auf 11, darunter der strukturelle Test „kein Pfad außerhalb der Schreibmenge" und Abbruchtests für die fünf Vorbedingungen. Das alte Kriterium zum Budget-Gate ist in die Designpunkte gewandert (es war eine Entscheidung, kein Kriterium). Beim CONTRACT-Kriterium präzisiert, dass
docs verifynur die Existenz einer backtick-Zelle prüft, nicht beide Tabellen.Labels:
status/blockedrunter (#42 geschlossen, damit kein Blocker mehr),prio/waiting→prio/planned,size/L→size/M— die offenen Designfragen, die das L trugen, sind mit dieser Fassung entschieden.Changelog: Zeile „Ohne lokalen Stamp" korrigiert. Der Verweis auf
migrate baselinewar falsch — das Kommando repariert.wikitool-kb.json(die Form des Inhalts) und sagt über die Herkunft der Maschinerie nichts; die beiden fehlenden Dateien waren in der Zeile zusammengezogen. Verweis jetzt nur noch aufupstream merge, plus die Begründung, warum ein Clone die Basis anderswo hat (Merge-Base statt Hashes).Neu unter „Bewusst offen gelassen": Stamp-Rekonstruktion aus einem alten Tarball. Der
files-Block ist genau die sha256-Summen des Export-Baums, und der Stamp steht nicht in seinem eigenen Block — das Tarball der Version X ist damit byteweise die Basis, die der Stamp der Version X hält. Eindist upgrade --from-release <altes-tarball>würde den Abbruch für eine Instanz ohne Stamp auflösen. Nicht entschieden, nicht Teil des ersten Schnitts, aber benannt, damit die Abbruchmeldung den Fall nicht als grundsätzlich unlösbar hinstellt.Ein Fallstrick ergänzt: der Export schreibt den Stamp nur ins Ziel, nie in die Quelle — dieser Checkout hat also keinen, und das ist kein Defekt. Für
version checkfolgenlos (update_url()fällt aufDEFAULT_UPDATE_URLzurück), fürupgradekonstituierend. Erkennung geht ohne Stamp, Anwendung nicht.Changelog: Umgesetzt und geschlossen. Body auf Endstand gebracht -
Ergebnisoben (Commit, Version, CI-Läufe, Testergebnis), Design-Abschnitte auf „wie umgesetzt" umgestellt,Bewusst nicht umgesetztbenennt die beiden zurückgestellten Punkte aus der Spezifikation mit Begründung, alle elf Akzeptanzkriterien abgehakt mit den Tests, die sie decken.Commit
cd81ba3(main),4.5.0-beta.4. Verifiziert:docs verify,instructions verify, volle Testsuite (963 grün, 24 neu), einmal gegen eine leere Maschine; CI-Läufe #147/#148 grün.Nachtrag nach dem Schließen —
stack-devSchritt 6 zu Ende geführt. Der Abschluss oben hat diedocs/-Veralterungsprüfung benannt statt sie durchzuführen. Nachgeholt, mit einem Fund; Commit368438e,4.5.0-beta.5, CI-Läufe #149/#150 grün.docs/ownership-and-templates.md§ „The consequence in practice" war durch dieses Issue veraltet. Die Seite beschrieb ein Upgrade als Zweiteilung (verbatim überschreiben,.template-gestützte liegen lassen) und begründete den ersten Teil damit, verbatim ausgelieferte Dateien seien „safe to replace wholesale", weil sie „never instance-specific to begin with" waren — genau die Annahme, diedist upgradebewusst nicht trifft. Dazu fehlte die dritte Klasse ganz: die einmalig gesäten, danach instanzeigenen Pfade. Beides korrigiert.Dazu eine Präzisierung in
tools/CONTRACT.md, die vorher nirgends stand: nach--keep-localwird der neue Stamp trotzdem vollständig geschrieben und trägt die Release-Summe auch für Dateien, die bewusst nicht geschrieben wurden. Er ist Vergleichsbasis, kein Inventar der Platte — und genau das hält eine übersprungene Datei bei jedem weiteren Lauf als abweichend gemeldet.Modellaufteilung, damit sie nicht stillschweigend bleibt: Spezifikation auf Opus, Umsetzung und Abschluss auf Sonnet, dieser Nachtrag auf Opus. Der übersprungene
docs/-Check fiel genau in die Phase, für die Schritt 6 den Rücksprung vorsieht — das ist kein Zufall, sondern der Beleg dafür, dass die Bruchstelle im Skill an der richtigen Stelle sitzt.