stack: Upgrade-Pfad bekommt eine eigene manual-Instruktion, INSTALL.md verweist darauf (#108)
Files changed: - CHANGES.md - INSTALL.md - VERSION - instructions/session-setup.md - instructions/upgrade-instance.md - tools/CONTRACT.md - tools/chemenu/commands/dist_cmd.py - tools/chemenu/tests/test_dist_upgrade.py
This commit is contained in:
+64
@@ -59,6 +59,70 @@ concern - readable here, never shipped as something to parse.
|
||||
|
||||
---
|
||||
|
||||
## 6.1.0-beta.1 - 2026-09-16 - Upgrade-Prozedur als eigene Instruktion statt als Prosa in INSTALL.md
|
||||
|
||||
**Author:** Torben Nehmer
|
||||
|
||||
<!-- wikitool:bumps -->
|
||||
- Upgrade-Prozedur als eigene Instruktion statt als Prosa in INSTALL.md
|
||||
<!-- /wikitool:bumps -->
|
||||
|
||||
### Upgrade-Prozedur als eigene Instruktion statt als Prosa in INSTALL.md
|
||||
|
||||
Der Upgrade-Pfad einer ausgelieferten Instanz stand nur in INSTALL.md § "Eine Instanz
|
||||
aktualisieren" - einem Dokument fuer Menschen, das `AGENTS.md` § File naming ausdruecklich als
|
||||
*"never by an agent as instruction"* fuehrt. Ausgefuehrt wird er aber von einer Agent-Sitzung,
|
||||
jedes Mal. Der getracete 5.0.0-auf-6.0.0-Lauf auf einer echten Instanz zeigt, was daraus folgt:
|
||||
der erste Tool-Call listete `instructions/` mit, fand keine passende Instruktion, oeffnete die
|
||||
naechstliegende (`private-instance.md`, der falsche der beiden Wege) und fiel dann auf INSTALL.md
|
||||
zurueck. `migrate verify --from <commit vor dem Tausch>` - INSTALL.md Schritt 6, erster
|
||||
Pruefschritt - lief in 33 Werkzeugaufrufen kein einziges Mal, und die Agent-Sitzung wurde nie neu
|
||||
gestartet, obwohl `AGENTS.md` im selben Commit +44/-3 bekommen hatte. Die anschliessende Migration
|
||||
lief damit unter dem alten Kontrollplan.
|
||||
|
||||
Dahinter lagen drei Reihenfolgen nebeneinander: die in INSTALL.md, die im Abschlussbericht von
|
||||
`dist upgrade`, und die tatsaechlich gelaufene. Genau der Zustand, den Invariante 8 verbietet.
|
||||
|
||||
`instructions/upgrade-instance.md` ist jetzt die eine Fassung: dreizehn Schritte von der
|
||||
Sitzungs-Id bis zum zweiten Publish, mit dem Sitzungsneustart an der Stelle, an der der neue
|
||||
Kontrollplan zu gelten anfaengt - nach dem Publish der Maschinerie, vor der Migrationskette, und
|
||||
mit `migrate status` als Wiedereinstiegspunkt fuer die neue Sitzung. `manual: true`, weil die
|
||||
Prozedur einmal pro Release laeuft und nie implizit aufgegriffen werden darf; ein Skill wuerde
|
||||
seine `description` dafuer in jede Sitzung legen. Auffindbar ist sie ueber den Abschlussbericht
|
||||
von `dist upgrade`, der statt einer eigenen Schrittliste jetzt die Datei nennt und das Kommando,
|
||||
bei dem der Lauf weitergeht (`instructions sync`). INSTALL.md behaelt, was ein Mensch *vorher*
|
||||
entscheidet, und den einen Sonderfall, den die Instruktion nicht abdecken kann, weil es sie dort
|
||||
noch nicht gibt: den ersten Sprung auf `4.5.0`.
|
||||
|
||||
Zwei Schritte der Instruktion sagen ausdruecklich, dass sie eine Luecke umgehen, und was sie
|
||||
ueberfluessig macht. Schritt 2 liest die Release-Notes von der Release-Seite statt mit
|
||||
`version notes`, weil eine Instanz ihre `CHANGES.md` als Stub bekommt und `dist upgrade` sie nie
|
||||
ueberschreibt - der Befehl kann dort nicht heute und nicht spaeter antworten. Schritt 6 nimmt fuer
|
||||
eine lokal veraenderte stackeigene Datei die Release-Fassung von Hand, weil es zu `--keep-local`
|
||||
kein Gegenstueck gibt; dabei geht der noetige Commit ueber `publish --no-push`, nicht ueber
|
||||
`git commit` - Invariante 5 kennt keine Ausnahme fuer "ist ja nur eine Vorbedingung", und genau
|
||||
diese Ausnahme hat sich der beobachtete Lauf genommen.
|
||||
|
||||
`instructions/session-setup.md` sagt jetzt, dass ein `export` nur traegt, solange die Shell
|
||||
traegt. Mehrere Harnesses starten pro Tool-Call eine frische Shell - das Arbeitsverzeichnis
|
||||
ueberlebt, Shell-State nicht - und dann faellt jeder Aufruf auf seine eigene Parent-PID zurueck.
|
||||
Im gemessenen Lauf wurde eine Sitzung so zu 21 Telemetrie-Buckets mit hoechstens drei Aufrufen
|
||||
pro Bucket: das Iteration-Budget-Gate (60) und der Loop-Breaker (3 identische in Folge) konnten
|
||||
strukturell nicht ausloesen. Die Anleitung nennt deshalb die Inline-Form pro Aufruf und den
|
||||
Einzeiler, mit dem sich beantworten laesst, welcher Fall vorliegt.
|
||||
|
||||
Verifiziert: `docs verify` (73 ausgelieferte Dokumente, 58 Referenzdateien),
|
||||
`instructions verify` (23 Instruktionen, 7 Skills) und 1276 Tests gruen - einer davon neu und auf
|
||||
genau die Stelle gerichtet, an der die Doppelung wieder entstehen wuerde: der Abschlussbericht
|
||||
von `dist upgrade` muss die Instruktion und ihr Wiedereinstiegskommando nennen, nicht eine zweite
|
||||
Kopie der Liste.
|
||||
|
||||
Kein Grenzuebertritt: eine neue Instruktionsdatei und ein geaenderter Meldungstext sind in beide
|
||||
Richtungen ein Drop-in. Eine Instanz, die zurueckgeht, behaelt die Datei als ueberzaehlige Datei,
|
||||
und nichts liest sie automatisch - `manual: true` heisst genau das.
|
||||
|
||||
---
|
||||
|
||||
## 6.0.1 - 2026-09-16 - docs toc/verify erreichen die .template-Form einer Referenzdatei
|
||||
|
||||
**Author:** Torben Nehmer
|
||||
|
||||
+28
-64
@@ -172,7 +172,10 @@ Update von 1.x auf 2.0.0" unten ist genau dieser Fall.
|
||||
Deshalb stehen in den Release-Notes eines MAJOR zwei getrennte Zeilen, und beide sind vor dem
|
||||
Update zu lesen: **Breaking Change:** sagt, was aufhört zu funktionieren und was diese Instanz
|
||||
dagegen tun muss; **Migration:** sagt, ob und wie der Korpus umgeschrieben wird (`none required`,
|
||||
wenn nicht). `tools/wikitool version notes` druckt den Eintrag.
|
||||
wenn nicht). `tools/wikitool version notes` druckt den Eintrag - allerdings nur dort, wo eine
|
||||
gefüllte `CHANGES.md` liegt, also im Ursprungs-Repo. Eine ausgelieferte Instanz bekommt die
|
||||
Datei als Stub und liest die Notes stattdessen auf der Release-Seite, die
|
||||
`.wikitool-release.json` als `release_url` nennt.
|
||||
|
||||
### Eine Instanz aktualisieren
|
||||
|
||||
@@ -180,81 +183,42 @@ Zwei Wege, je nachdem, wie diese Instanz entstanden ist. Ein **Clone mit gemeins
|
||||
Git-History** (`upstream`-Remote auf das Ursprungs-Repo, siehe
|
||||
[instructions/private-instance.md](instructions/private-instance.md)) nimmt Stack-Updates per
|
||||
echtem Drei-Wege-Merge: `tools/wikitool upstream merge`. Alles Folgende gilt für eine **Instanz
|
||||
aus einem Tarball**, ohne gemeinsame History - der Weg unten unter „Eine Instanz aktualisieren"
|
||||
nutzt sie.
|
||||
aus einem Tarball**, ohne gemeinsame History.
|
||||
|
||||
Das Anwenden eines Updates schreibt in eine Instanz, die bereits Inhalt hat. Der Inhalt hat dabei
|
||||
eine **eigene Version**: `.wikitool-kb.json` sagt, in welcher Form die Seiten vorliegen,
|
||||
unabhängig davon, welche Maschinerie danebensteht. Genau dieser Unterschied ist der Zustand, in
|
||||
dem sich jede Instanz mitten im Upgrade befindet.
|
||||
|
||||
1. **Vor dem Tausch** prüfen, was ansteht - solange `VERSION` noch die alte ist:
|
||||
**Die Durchführung selbst steht in `instructions/upgrade-instance.md`** - die Reihenfolge, was
|
||||
jeder Schritt entscheidet, wo die Agent-Sitzung neu gestartet werden muss, und die beiden Stellen,
|
||||
an denen heute Handarbeit nötig ist. Sie steht dort und nicht hier, weil sie von einer
|
||||
Agent-Sitzung ausgeführt wird; eine zweite Fassung derselben Schrittfolge an dieser Stelle wäre
|
||||
genau die Kopie, die irgendwann auseinanderläuft. Wer den Lauf selbst fahren will, liest dieselbe
|
||||
Datei.
|
||||
|
||||
```bash
|
||||
tools/wikitool migrate status
|
||||
```
|
||||
Was dieses Dokument beiträgt, ist die Entscheidung *davor* - welches Release, ob überhaupt, woher
|
||||
der Tarball kommt (§ „Version und Updates" und Weg A oben) - und der eine Sonderfall, den die
|
||||
Instruktion nicht abdecken kann, weil es sie dort noch nicht gibt:
|
||||
|
||||
Steht hier etwas aus, erst diese Migrationskette abschließen (Schritt 5 unten) - `dist upgrade`
|
||||
verweigert den Tausch sonst von selbst.
|
||||
**Beim ersten Sprung auf `4.5.0` oder höher gibt es `dist upgrade` in der Instanz noch nicht** -
|
||||
es kam erst mit `4.5.0`. Dann das Werkzeug aus dem entpackten *neuen* Tarball verwenden, gegen die
|
||||
alte Instanz gerichtet:
|
||||
|
||||
2. Release-Tarball herunterladen und die Release-Notes lesen (Weg A oben).
|
||||
3. **Maschinerie tauschen:**
|
||||
|
||||
```bash
|
||||
tools/wikitool dist upgrade <tarball-oder-verzeichnis> --dry-run
|
||||
```
|
||||
|
||||
**Beim ersten Sprung auf `4.5.0` oder höher gibt es dieses Kommando in der Instanz noch
|
||||
nicht** - es kam erst mit `4.5.0`. Dann das Werkzeug aus dem entpackten *neuen* Tarball
|
||||
verwenden, gegen die alte Instanz gerichtet:
|
||||
|
||||
```bash
|
||||
tar -xzf chemenu-stack-<version>.tar.gz
|
||||
CHEMENU_ROOT="$PWD" chemenu-stack-<version>/tools/wikitool \
|
||||
```bash
|
||||
tar -xzf chemenu-stack-<version>.tar.gz
|
||||
CHEMENU_ROOT="$PWD" chemenu-stack-<version>/tools/wikitool \
|
||||
dist upgrade chemenu-stack-<version>.tar.gz --dry-run
|
||||
```
|
||||
```
|
||||
|
||||
`CHEMENU_ROOT` sagt dem Paket, auf welchen Korpus es zeigen soll (siehe § Konfiguration);
|
||||
ohne die Variable würde es den entpackten Tarball selbst für die Instanz halten. Ab dem
|
||||
zweiten Upgrade trägt die Instanz das Kommando selbst und die kurze Form oben genügt.
|
||||
`CHEMENU_ROOT` sagt dem Paket, auf welchen Korpus es zeigen soll (siehe § Konfiguration); ohne die
|
||||
Variable würde es den entpackten Tarball selbst für die Instanz halten. Ab dem zweiten Upgrade
|
||||
trägt die Instanz Kommando und Instruktion selbst, und der normale Weg greift.
|
||||
|
||||
Klassifiziert jede Datei aus dem `files`-Block der neuen `.wikitool-release.json`:
|
||||
unverändert seit der Installation, lokal verändert oder gelöscht, neu im Release, oder aus dem
|
||||
Release entfallen - und druckt die Migrationskette, die nach dem Tausch aussteht, ohne sie
|
||||
auszuführen. Ohne `--dry-run` schreibt der Befehl; eine lokal veränderte oder gelöschte Datei
|
||||
wird dabei **nie** stillschweigend überschrieben - der Lauf bricht mit der vollständigen Liste
|
||||
ab, es sei denn `--keep-local` ist gesetzt (dann bleibt jede davon unangetastet, erneut
|
||||
gemeldet). `--prune` entfernt zusätzlich Dateien, die der neue Release nicht mehr ausliefert
|
||||
und die seit der Installation unverändert sind. Voraussetzungen: ein sauberer Arbeitsbaum
|
||||
(kein Git-Repo ist ein WARN, keine Sperre), eine lokale `.wikitool-release.json` mit
|
||||
`files`-Block (fehlt sie, siehe „Fallstricke" unten), und `.wikitool-kb.json` vorhanden.
|
||||
Committet und pusht nichts (Invariante 5). Vollständiger Fehlerkontrakt:
|
||||
[tools/CONTRACT.md](tools/CONTRACT.md).
|
||||
|
||||
Eine lokal veränderte Stack-Datei ist damit sichtbar, statt von Hand gegen die sha256-Summen
|
||||
im `files`-Block geprüft werden zu müssen - genau der Schritt, der vor `4.5.0` hier stand.
|
||||
4. Bei einer Kompatibilitätsgrenze (`dist upgrade` meldet sie laut) die Release-Notes vor dem
|
||||
nächsten Schritt lesen: **Breaking Change:** und **Migration:** im Eintrag von
|
||||
`tools/wikitool version notes` sagen, was aufhört zu funktionieren und ob der Korpus
|
||||
umgeschrieben werden muss.
|
||||
5. **Die Migrationskette abarbeiten.** `tools/wikitool migrate status` listet jetzt alle
|
||||
offenen Migrationen in der Reihenfolge, in der sie laufen müssen - bei einem Sprung über
|
||||
mehrere Versionen sind das mehrere. Für jede: das genannte Dokument unter
|
||||
`instructions/migrations/` ausführen lassen (die Prozedur dazu ist
|
||||
`instructions/migrate-corpus.md`), dann
|
||||
|
||||
```bash
|
||||
tools/wikitool migrate done <version>
|
||||
```
|
||||
|
||||
`done` verweigert jede Version, die nicht das nächste Glied ist - eine übersprungene
|
||||
Migration hinterlässt einen Korpus in einer Form, die keine Version beschreibt. Ein
|
||||
abgebrochenes Upgrade wird durch erneutes `migrate status` fortgesetzt.
|
||||
6. Prüfen: `tools/wikitool migrate verify --from <commit vor dem Tausch>`, dann `doctor`,
|
||||
`docs verify`, `instructions verify` und `lint`. Zum Schluss
|
||||
`tools/wikitool instructions sync` (die Skills sind Kopien) und die Agent-Session neu
|
||||
starten. `dist upgrade` nennt diese Reihenfolge im eigenen Abschlussbericht, führt aber keinen
|
||||
der Schritte selbst aus.
|
||||
Was `dist upgrade` dabei genau tut, klassifiziert und verweigert, steht in
|
||||
[tools/CONTRACT.md](tools/CONTRACT.md) - einschließlich des vollständigen Fehlerkontrakts. Eine
|
||||
lokal veränderte Stack-Datei ist damit sichtbar, statt von Hand gegen die sha256-Summen im
|
||||
`files`-Block geprüft werden zu müssen - genau der Schritt, der vor `4.5.0` hier stand.
|
||||
|
||||
`doctor` warnt, solange `kb_version` hinter `VERSION` zurückliegt und noch Migrationen offen
|
||||
sind. Einer Instanz, die älter ist als `.wikitool-kb.json`, fehlt die Datei ganz - dann einmalig
|
||||
|
||||
@@ -23,6 +23,21 @@ export WIKITOOL_SESSION_ID="wiki-$(date +%s)"
|
||||
tools/wikitool sync
|
||||
```
|
||||
|
||||
**An `export` only carries if the shell carries.** Several agent harnesses run every tool call in
|
||||
a freshly initialised shell: the working directory survives, shell state - environment variables,
|
||||
functions - does not, so the variable is gone by the next call and each call falls back to its own
|
||||
parent pid. The budget then counts a run of thirty calls as thirty sessions of one, and neither
|
||||
the 60-call ceiling nor the loop-breaker can ever trip. On such a harness, pass the id **inline on
|
||||
every call** instead, keeping the same value for the whole task:
|
||||
|
||||
```bash
|
||||
WIKITOOL_SESSION_ID="wiki-1234" tools/wikitool sync
|
||||
WIKITOOL_SESSION_ID="wiki-1234" tools/wikitool new entity --name "..."
|
||||
```
|
||||
|
||||
Which of the two applies is answerable in one call: run `tools/wikitool budget status` twice in
|
||||
separate calls after exporting, and see whether it names the same id both times.
|
||||
|
||||
Check the current state at any time with `tools/wikitool budget status`, which is never
|
||||
counted against the budget itself and prints the id it is counting under.
|
||||
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
---
|
||||
type: types/instruction.md
|
||||
name: upgrade-instance
|
||||
description: Carry out a stack release upgrade on an instance built from a tarball - read this release's notes, swap the machinery with dist upgrade, work the migration chain, verify, publish, and restart the session at the point where the new control plane starts to matter.
|
||||
manual: true
|
||||
---
|
||||
# Upgrade this instance to a new stack release
|
||||
|
||||
An instance built from a `dist export` tarball takes stack updates by copying a newer release
|
||||
over its machinery. This is the order in which that happens, what each step decides, and where
|
||||
the two known rough edges are. It ends with the instance on the new `VERSION`, its content
|
||||
version recorded, every check green, and the change published.
|
||||
|
||||
**This is the tarball path.** An instance that is a *clone* of the origin repo, sharing git
|
||||
history, takes updates by three-way merge (`tools/wikitool upstream merge`) and follows
|
||||
[private-instance.md](private-instance.md) instead. `git remote -v` answers which one this is:
|
||||
a clone carries an `upstream` remote pointing at the origin.
|
||||
|
||||
**Two things this file deliberately does not know.** First, the copy you are reading shipped
|
||||
with the release this instance is *leaving*, not the one it is going to - so nothing specific to
|
||||
a particular jump is written here. That belongs to the release notes (step 2) and to the
|
||||
migration documents that arrive inside the tarball. Second, two steps below work around gaps in
|
||||
the tooling rather than describing its intended shape; each says so, and says what makes it
|
||||
unnecessary.
|
||||
|
||||
<!-- wikitool:toc -->
|
||||
## Contents
|
||||
|
||||
- [When to run](#when-to-run)
|
||||
- [Steps](#steps)
|
||||
- [Decision points](#decision-points)
|
||||
- [Scope](#scope)
|
||||
<!-- /wikitool:toc -->
|
||||
|
||||
## When to run
|
||||
|
||||
- `tools/wikitool version check` reports `state: update` or `state: migration`, and the operator
|
||||
wants the new release installed.
|
||||
- An operator asks for the stack, the tooling or "the wiki software" to be brought up to date.
|
||||
- An interrupted upgrade is being resumed. Do not restart from step 1: `migrate status` and
|
||||
`dist upgrade --dry-run` both report the true state, and the step that matches what they say
|
||||
is where this run continues.
|
||||
|
||||
Not for setting up a new instance ([setup-instance.md](setup-instance.md)), not for preparing a
|
||||
fresh clone ([bootstrap.md](bootstrap.md)), and not for the clone-with-upstream path above.
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Take a session id and pass it on every call for the whole upgrade** - the form and the
|
||||
reason are in [session-setup.md](session-setup.md). An upgrade is one of the longest runs
|
||||
this stack has, and the iteration budget only sees it as one run if every call carries the
|
||||
same id:
|
||||
|
||||
```bash
|
||||
WIKITOOL_SESSION_ID=upgrade-<target-version> tools/wikitool version check
|
||||
```
|
||||
|
||||
2. **Read this release's notes before touching anything.** Two lines decide the rest of the run:
|
||||
**Breaking Change:** says what stops working and what this instance must do about it, and
|
||||
**Migration:** says whether the corpus has to be rewritten (`none required` when it does not).
|
||||
|
||||
`tools/wikitool version notes` is the command for that - **and on a distributed instance it
|
||||
cannot answer**. It reads the local `CHANGES.md`, which an instance receives as a stub with no
|
||||
version entries and which `dist upgrade` never overwrites, so the answer is an `ERROR` today
|
||||
and at every future release. Read the release page instead: `.wikitool-release.json` carries
|
||||
`release_url` for the human page and `update_url` for the JSON feed. This paragraph stops
|
||||
being necessary the day `version notes` falls back to that feed.
|
||||
|
||||
3. **Ask what is already outstanding, while `VERSION` is still the old one:**
|
||||
|
||||
```bash
|
||||
tools/wikitool migrate status
|
||||
```
|
||||
|
||||
Anything in the outstanding chain is finished **before** the swap - `dist upgrade` refuses
|
||||
otherwise, and a chain that was already owed is not this release's business. The procedure is
|
||||
step 12's, run against the migration documents this instance already has. An `offered` upgrade
|
||||
listed separately blocks nothing and is decided later, in step 12.
|
||||
|
||||
4. **Fetch the tarball and verify it.** `dist upgrade` downloads nothing; the file has to be
|
||||
there already. Take the `.tar.gz` and its `.sha256` from the release page found in step 2 and
|
||||
check them before unpacking. A tarball must unpack to exactly one top-level directory.
|
||||
|
||||
5. **Dry-run the swap and read all four counts:**
|
||||
|
||||
```bash
|
||||
tools/wikitool dist upgrade <tarball> --dry-run
|
||||
```
|
||||
|
||||
`unchanged` / `new` / `locally changed` / `removed from the release`. The first two need no
|
||||
decision. `locally changed` is step 6. `removed` matters only if `--prune` is wanted, which is
|
||||
optional and never required.
|
||||
|
||||
6. **Only if a file is reported as locally changed: decide whose file it is, then reconcile it.**
|
||||
The classification is against the sha256 the *installed* release recorded, so "locally
|
||||
changed" means the working tree differs from what this instance was given - deliberately or
|
||||
by a stray editor save.
|
||||
|
||||
| Whose file | What to do |
|
||||
|---|---|
|
||||
| The instance's own | Cannot appear here, which is worth knowing so a report that looks like it is read again rather than acted on: a file the instance owns either ships only as `<name>.template` (`kb/CONVENTIONS.md`, each `COLLECTION.md`, `USER.md`/`SOUL.md`/`ENVIRONMENT.md`) and is never classified at all, or is seeded once and then kept out of the write set (`.wikitool-kb.json`, `CHANGES.md`) |
|
||||
| Machinery (a `CONTRACT.md`, anything under `tools/`, `types/`, `instructions/`, `AGENTS.md`, and every `<name>.template` beside an owned file) | It should not have local changes at all. Take the release's version, per the recipe below |
|
||||
| Machinery this instance changed **on purpose** | `--keep-local` keeps every listed file untouched - but the new stamp records the release digest anyway, so the same file is reported again at every future upgrade. That is the right answer only for a difference the instance intends to carry indefinitely |
|
||||
|
||||
Taking the release's version has no flag today, so it is three moves - and the middle one
|
||||
exists only because the repair dirties the tree that the next command requires clean:
|
||||
|
||||
```bash
|
||||
tar -xzf <tarball> -C <scratch-dir>
|
||||
cp <scratch-dir>/<top-level-dir>/<path> <path>
|
||||
tools/wikitool publish --message "stack: revert local drift on <path>" --no-push
|
||||
tools/wikitool dist upgrade <tarball> --dry-run # now 0 locally changed
|
||||
```
|
||||
|
||||
**The commit goes through `publish`, not `git commit`** - AGENTS.md invariant 5 has no
|
||||
exception for "it is only a precondition", and `--no-push` is what keeps this from being a
|
||||
push nobody asked for. This whole recipe disappears the day `dist upgrade` grows a
|
||||
counterpart to `--keep-local` that writes the release's version instead of aborting.
|
||||
|
||||
7. **Swap the machinery.** Note the commit the instance is on first - step 13 compares against
|
||||
it:
|
||||
|
||||
```bash
|
||||
git rev-parse --short HEAD # the pre-swap commit; keep it
|
||||
tools/wikitool dist upgrade <tarball>
|
||||
```
|
||||
|
||||
It writes, and commits nothing.
|
||||
|
||||
8. **Republish the skills.** `tools/wikitool instructions sync` - the published skill directories
|
||||
are copies, so until this runs the harness is still offering the previous release's skills.
|
||||
|
||||
9. **Verify the machinery, and fix what the release said would need fixing:**
|
||||
|
||||
```bash
|
||||
tools/wikitool doctor
|
||||
tools/wikitool docs verify
|
||||
tools/wikitool instructions verify
|
||||
tools/wikitool lint
|
||||
```
|
||||
|
||||
A `docs verify` failure naming a missing or stale table of contents is repaired with
|
||||
`tools/wikitool docs toc --apply`, never by hand - a release that widened the set of files
|
||||
carrying a region will produce exactly that on files this instance adopted before the
|
||||
widening. Any other failure is read against step 2's **Breaking Change:** line: if the release
|
||||
predicted it, the notes also say what fixes it; if it did not, stop and report it rather than
|
||||
improvising.
|
||||
|
||||
10. **Publish the machinery swap.** A release swap is far above the Mass-Update Gate's threshold,
|
||||
so expect exit 42. That is not an error and not yours to clear: reproduce the file breakdown
|
||||
it prints for the operator, stop, and publish with the token it named once they have
|
||||
approved it. See [gates.md](gates.md).
|
||||
|
||||
Publishing here, before the content migrations, is deliberate. The intermediate state -
|
||||
new machinery, content still at the old shape - is a state the stack names rather than
|
||||
avoids (`.wikitool-kb.json` records it), and it keeps a 200-file swap out of the same commit
|
||||
as a content rewrite.
|
||||
|
||||
11. **Restart the agent session.** Everything the previous steps replaced - `AGENTS.md`, the
|
||||
contracts, the type-specs, the skills - is still in the running session's context in its
|
||||
*old* form. A migration document written against a rule that arrived in this release will
|
||||
otherwise be carried out against the rule it replaced, and nothing checks that.
|
||||
|
||||
The new session resumes at step 12. `tools/wikitool migrate status` is the resume point:
|
||||
it is stateful, so it says what is left without being told what already happened.
|
||||
|
||||
12. **Work the migration chain.** `tools/wikitool migrate status` lists what is outstanding, in
|
||||
the order it has to run - a jump across several releases lists several. For each one, run
|
||||
the named document under `instructions/migrations/` following
|
||||
[migrate-corpus.md](migrate-corpus.md), then record it:
|
||||
|
||||
```bash
|
||||
tools/wikitool migrate done <version>
|
||||
```
|
||||
|
||||
An `offered` migration is a separate decision, not part of the chain: it changes a file this
|
||||
instance owns, blocks nothing, and recording it does not move `kb_version`. Take it or
|
||||
decline it deliberately; both are correct answers.
|
||||
|
||||
**Whatever the migration changes, capture the before.** Where a document asks that some
|
||||
command's output "read the same as before", that is only checkable if the before was written
|
||||
down - redirect it to a file first and `diff` afterwards, rather than reading two long
|
||||
outputs from memory. Reading either one through `head` or `tail` is how a difference in the
|
||||
middle survives the check.
|
||||
|
||||
13. **Verify the content, then publish.** Only after the chain has run, and against the commit
|
||||
noted in step 7:
|
||||
|
||||
```bash
|
||||
tools/wikitool migrate verify --from <pre-swap commit>
|
||||
tools/wikitool lint
|
||||
```
|
||||
|
||||
`migrate verify` is the only check that sees a page which lost a citation, a wikilink or a
|
||||
generated-region marker in the rewrite - `lint` reports a corpus that is internally
|
||||
consistent, which a corpus that quietly lost something still is. Then publish, the same way
|
||||
as in step 10.
|
||||
|
||||
## Decision points
|
||||
|
||||
- **`version check` reports `state: migration` (a compatibility boundary)?** That is a statement
|
||||
about the machinery being a drop-in replacement, not about the corpus. A boundary crossing with
|
||||
an empty migration chain is normal and means the hand-work is elsewhere - which is precisely
|
||||
what step 2's **Breaking Change:** line names.
|
||||
- **`dist upgrade` refuses because the tree is not clean?** Commit or stash what is there first,
|
||||
and look at what it is: work in progress is committed through `publish`, an editor's stray
|
||||
reformatting of machinery is step 6's case.
|
||||
- **A required migration cannot be completed now?** Stop after step 10 and leave it. The
|
||||
intermediate state is legitimate and `migrate status` resumes it; what is not legitimate is
|
||||
recording a migration with `migrate done` that was not carried out - the version then describes
|
||||
a shape the corpus is not in.
|
||||
- **`doctor` reports `kb-version` behind `VERSION` after everything is done?** Correct when the
|
||||
release's chain was empty or carried only `offered` entries: an offer changes a file the
|
||||
instance owns, not the shape of its content, so the content version stays where it was.
|
||||
|
||||
## Scope
|
||||
|
||||
For an instance that receives releases as tarballs. Not the origin repo, which has no upgrade
|
||||
path of its own, and not a clone with shared history - see the second paragraph. Anything about
|
||||
*writing* a migration document rather than running one is
|
||||
[migrate-corpus.md](migrate-corpus.md) § "Writing the migration document".
|
||||
|
||||
What a human decides before any of this starts - which release, whether to take it at all, where
|
||||
the tarball comes from - is [INSTALL.md](../INSTALL.md) § "Version und Updates".
|
||||
+1
-1
@@ -185,7 +185,7 @@ tools/wikitool <command> --help
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `dist export <target> [--dry-run] [--source-repo U] [--source-commit SHA] [--release-url U] [--update-url U]` | Write a contentless, distributable copy of this repo's machinery into an empty `<target>` directory: `AGENTS.md`/`README.md`/`EVALS.md` with any `<!-- dist:strip-start -->...<!-- dist:strip-end -->` region removed, `instructions/` (minus `instructions/dev/`), `types/` (the `root: kb` page type-specs and their schemas re-keyed as `.template`, the stack's own verbatim), `docs/` verbatim, `tools/` (no venv/caches), the `.github/hooks/`+`.vibe/` session-tracing config plus `.claude/settings.json`, `kb/CONTRACT.md` (no pages, no areas), the two flat anchors `raw/.gitkeep` and `incoming/.gitkeep` (both roots are flat now that a file's location under `raw/` is a date shard rather than a hand-picked type, so a fresh export no longer creates any type subdirectories under either root; `incoming/.gitkeep` is trackable and survives becoming a git repository, so a plain clone gets the directory without any bootstrap step re-creating it), `VERSION`, `USER.md.template`/`SOUL.md.template` plus `kb/CONVENTIONS.md.template` and each collection's contract re-keyed as `kb/<name>/COLLECTION.md.template` (the templates ship; the filled `USER.md`/`SOUL.md`/`kb/CONVENTIONS.md`/`kb/<name>/COLLECTION.md`/`types/<page-type>.md` never do - all of them bind their instance and none are the stack's to decide, and `find_leaks` refuses a plan carrying one), and a generated `.wikitool-release.json` stamp (version, export date, origin, and a sha256 per exported file - the base a later upgrade would compare against). The four origin options only fill stamp fields: `export` never calls git and cannot discover them. Refuses a non-empty target, and a tree with no `VERSION`. See `instructions/setup-instance.md`. One-way: there is no command that reconstructs a distributed instance into a dev instance - work on the stack in the origin repo (or a new dev instance exported from it) instead |
|
||||
| `dist upgrade <source> [--dry-run] [--keep-local] [--prune] [--pre]` | Apply a stack update `dist export` produced - the write half of `version check`. Never downloads anything: `<source>` is an already-fetched export directory or `.tar.gz` release archive (verified against a sibling `.sha256` if one is present; WARNs, does not block, if it is absent), which must unpack to exactly one top-level directory - the shape `.gitea/workflows/release.yml` packs. The write set is exactly the *new* `.wikitool-release.json`'s `files` block, minus what an export re-seeds from a blank template every time (`kb/log.md`, `raw/.gitkeep` - `chemenu.ownership.is_export_stub`) or seeds once and the instance owns from then on (`.wikitool-kb.json`, `CHANGES.md` - `chemenu.ownership.is_upgrade_preserved`), plus the stamp itself, always rewritten. Every candidate path is classified against the *local* `.wikitool-release.json`'s recorded digest for it: unchanged is overwritten silently, absent from the old stamp is created, and locally modified or locally deleted is **never** silently overwritten - the run aborts with the full list unless `--keep-local` says to proceed and leave every one of them untouched. After a `--keep-local` run the new stamp is still written whole, so it records the release's digest for files that were deliberately *not* written: the stamp is the baseline for the next comparison, not a literal inventory of what is on disk. That is what keeps a skipped file diverging - and therefore reported - on every later run, rather than quietly reading as current once it has been skipped once. A path in the old stamp but not the new one is reported as no longer part of the release and left alone unless `--prune` is passed, which removes it only if it is still unchanged since installation. Reports the migration chain the new machinery would owe (`chemenu.kb_state.chain` over the *new* tree's `instructions/migrations/`, read via a `directory` argument to `load_migrations`) but never runs any of it - there is no `migrate run`. Refuses before touching the source at all when: `VERSION` or `.wikitool-release.json` (with a `files` block) is missing locally, `.wikitool-kb.json` is missing, a migration is already outstanding against the *installed* machinery, or the working tree is dirty (not being a git repository at all is a WARN, not a refusal). Refuses after reading the source when: it carries no `VERSION`/`.wikitool-release.json`/`files` block, its version is older than or equal to the installed one (equal is a no-op success), or it is a pre-release (`-beta.N`) without `--pre`. Reports, but does not block on, a crossed compatibility boundary. Never touches git - no commit, no push (invariant 5). See `INSTALL.md` § "Eine Instanz aktualisieren" |
|
||||
| `dist upgrade <source> [--dry-run] [--keep-local] [--prune] [--pre]` | Apply a stack update `dist export` produced - the write half of `version check`. Never downloads anything: `<source>` is an already-fetched export directory or `.tar.gz` release archive (verified against a sibling `.sha256` if one is present; WARNs, does not block, if it is absent), which must unpack to exactly one top-level directory - the shape `.gitea/workflows/release.yml` packs. The write set is exactly the *new* `.wikitool-release.json`'s `files` block, minus what an export re-seeds from a blank template every time (`kb/log.md`, `raw/.gitkeep` - `chemenu.ownership.is_export_stub`) or seeds once and the instance owns from then on (`.wikitool-kb.json`, `CHANGES.md` - `chemenu.ownership.is_upgrade_preserved`), plus the stamp itself, always rewritten. Every candidate path is classified against the *local* `.wikitool-release.json`'s recorded digest for it: unchanged is overwritten silently, absent from the old stamp is created, and locally modified or locally deleted is **never** silently overwritten - the run aborts with the full list unless `--keep-local` says to proceed and leave every one of them untouched. After a `--keep-local` run the new stamp is still written whole, so it records the release's digest for files that were deliberately *not* written: the stamp is the baseline for the next comparison, not a literal inventory of what is on disk. That is what keeps a skipped file diverging - and therefore reported - on every later run, rather than quietly reading as current once it has been skipped once. A path in the old stamp but not the new one is reported as no longer part of the release and left alone unless `--prune` is passed, which removes it only if it is still unchanged since installation. Reports the migration chain the new machinery would owe (`chemenu.kb_state.chain` over the *new* tree's `instructions/migrations/`, read via a `directory` argument to `load_migrations`) but never runs any of it - there is no `migrate run`. Refuses before touching the source at all when: `VERSION` or `.wikitool-release.json` (with a `files` block) is missing locally, `.wikitool-kb.json` is missing, a migration is already outstanding against the *installed* machinery, or the working tree is dirty (not being a git repository at all is a WARN, not a refusal). Refuses after reading the source when: it carries no `VERSION`/`.wikitool-release.json`/`files` block, its version is older than or equal to the installed one (equal is a no-op success), or it is a pre-release (`-beta.N`) without `--pre`. Reports, but does not block on, a crossed compatibility boundary. Never touches git - no commit, no push (invariant 5). The closing report carries no step list of its own: everything after the swap is one order, written in `instructions/upgrade-instance.md`, which the report names and which resumes at `instructions sync`. What a human decides *before* the swap - which release, whether to take it, where the tarball comes from - is `INSTALL.md` § "Version und Updates" |
|
||||
| `version show [--json]` | Print this instance's stack version and where it came from (development tree, or a distribution with its export date and origin). Bare `wikitool version` is an alias for this. Read-only, offline, and **exempt from the Iteration Budget Gate** |
|
||||
| `version check [--url U] [--timeout S] [--json]` | Ask the origin's release feed whether a newer stack exists, and whether the step crosses a compatibility boundary (`state: current\|update\|migration\|ahead`). **The only command in `wikitool` that makes a network call** - never reached implicitly from another command, needs no key, times out, and reports an unreachable feed as an error rather than as "up to date". The feed is `$WIKITOOL_UPDATE_URL`, else the release stamp's, else the built-in origin; `$WIKITOOL_UPDATE_TOKEN` is only needed if that feed is not readable anonymously. Read-only and exempt from the budget gate |
|
||||
| `version notes [--version X.Y.Z]` | Print one version's `CHANGES.md` entry, for use as release notes (default: this tree's `VERSION`). Read-only and exempt from the budget gate |
|
||||
|
||||
@@ -1045,8 +1045,13 @@ def run_upgrade(
|
||||
summary += (
|
||||
f" {len(migration_chain)} migration(s) now outstanding - run `wikitool migrate status`."
|
||||
)
|
||||
# One pointer rather than a second copy of the order: the steps after the
|
||||
# swap live in instructions/upgrade-instance.md, which ships with every
|
||||
# instance. Naming the resume *command* rather than a step number keeps this
|
||||
# line correct when that file's numbering moves.
|
||||
summary += (
|
||||
" Nothing was committed. Now run, in order: `wikitool instructions sync`, `doctor`, "
|
||||
"`docs verify`, `instructions verify`, `lint` - then restart the agent session."
|
||||
" Nothing was committed and nothing is verified yet."
|
||||
" `instructions/upgrade-instance.md` carries the order for everything that follows"
|
||||
" and resumes at `wikitool instructions sync`."
|
||||
)
|
||||
success(summary)
|
||||
|
||||
@@ -192,6 +192,24 @@ def test_unchanged_and_new_files_are_written_silently(instance, tmp_path):
|
||||
assert stamp["files"]["types/entity.md"] == _digest("new\n")
|
||||
|
||||
|
||||
def test_closing_report_points_at_the_upgrade_instruction(instance, tmp_path, capsys):
|
||||
"""Everything after the swap has exactly one written order, and it is not
|
||||
this line: the report names the instruction that carries it and the command
|
||||
the run resumes at, rather than a second copy of the list that drifts
|
||||
(AGENTS.md invariant 8). A run that reads only this output must still be
|
||||
able to find the rest."""
|
||||
release = _release(
|
||||
tmp_path, "release", "1.1.0",
|
||||
{"AGENTS.md": "core\n", "tools/wikitool": "#!/bin/sh\n"},
|
||||
)
|
||||
|
||||
dist_cmd.run_upgrade(release)
|
||||
|
||||
out = " ".join(capsys.readouterr().out.split()) # rich wraps; rejoin first
|
||||
assert "instructions/upgrade-instance.md" in out
|
||||
assert "instructions sync" in out
|
||||
|
||||
|
||||
@pytest.mark.parametrize("preserved", [".wikitool-kb.json", "CHANGES.md", "kb/log.md", "raw/notes/.gitkeep"])
|
||||
def test_seeded_once_paths_are_never_written_even_if_the_release_stamp_lists_them(
|
||||
instance, tmp_path, preserved
|
||||
|
||||
Reference in New Issue
Block a user