Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b2f7dec122 | |||
| 29063f511b |
+44
@@ -20,6 +20,50 @@ their date-only headings.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2.2.1 - 2026-09-01 - private-instance: der Demo-Korpus wandert beim Merge doch mit - Prozedur korrigiert
|
||||||
|
|
||||||
|
**Author:** Torben Nehmer
|
||||||
|
|
||||||
|
`instructions/private-instance.md` behauptete in 2.2.0, ein `git merge upstream/main` löse
|
||||||
|
Änderungen am Demo-Korpus stillschweigend auf, weil die private Instanz ihn einmal gelöscht hat:
|
||||||
|
*deleted-in-ours, unmodified-in-theirs*. Das war **nicht gemessen, sondern angenommen** — und es
|
||||||
|
ist falsch. Ein Nachbau mit einem Upstream, der seinen Korpus bewegt, zeigt drei verschiedene
|
||||||
|
Verhalten:
|
||||||
|
|
||||||
|
| Upstream tut | `git merge upstream/main` tut |
|
||||||
|
|---|---|
|
||||||
|
| ändert eine Seite, die du gelöscht hast | `CONFLICT (modify/delete)` — und **lässt die Upstream-Fassung im Arbeitsbaum liegen**. Ein `git add -A` beim Auflösen holt die Demo-Seite zurück. |
|
||||||
|
| legt eine neue Seite an | staged sie **stillschweigend**. Kein Konflikt, keine Meldung. |
|
||||||
|
| löscht eine Seite, die du auch gelöscht hast | nichts. Der einzige harmlose Fall. |
|
||||||
|
|
||||||
|
Die mittlere Zeile ist die gefährliche, weil nichts sie ankündigt. Ein Upstream, der einen
|
||||||
|
Demo-Korpus ausliefert **und** ihn als Testbett benutzt, legt Seiten an — und jede einzelne
|
||||||
|
landet in der privaten Instanz und taucht dort in `lint`, `index`, `search` und
|
||||||
|
`confidence decay` auf. Genau diese Doppelnutzung beschreibt Issue #28.
|
||||||
|
|
||||||
|
**Korrigiert:** Die Update-Prozedur hält den Merge mit `--no-commit` offen, zwingt `kb/` und
|
||||||
|
`raw/` danach auf den eigenen Stand zurück (`git rm --cached`, `rm -rf`, `git checkout HEAD --`)
|
||||||
|
und schließt ihn erst dann. Solange der Merge offen ist, zeigt `HEAD` noch auf den Commit davor,
|
||||||
|
und genau das macht den Schritt sauber. Anschließend eine Kontrolle, die man nicht überlesen
|
||||||
|
kann:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git diff --name-only $BEFORE HEAD -- kb raw # muss leer sein
|
||||||
|
```
|
||||||
|
|
||||||
|
Das Rezept ist wörtlich so nachgespielt worden, wie es jetzt in der Datei steht — mit einem
|
||||||
|
Upstream, der gleichzeitig eine Seite ändert, eine anlegt, eine löscht und dasselbe unter
|
||||||
|
`raw/` tut. Ergebnis: Stack aktualisiert, nur eigener Inhalt übrig, Kontrolle leer,
|
||||||
|
Arbeitsbaum sauber.
|
||||||
|
|
||||||
|
**Auch die Decision Points korrigiert.** „Konflikt in `kb/` per Hand als *keep deleted*
|
||||||
|
auflösen" stand vorher da und ist der Rat, der in die Falle führt: `git add -A` committet die
|
||||||
|
Fassung, die git im Arbeitsbaum liegen gelassen hat.
|
||||||
|
|
||||||
|
**Dateien:** `instructions/private-instance.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 2.2.0 - 2026-09-01 - Publish-Remote Gate: publish schreibt nur an erklaerte Ziele
|
## 2.2.0 - 2026-09-01 - Publish-Remote Gate: publish schreibt nur an erklaerte Ziele
|
||||||
|
|
||||||
**Author:** Torben Nehmer
|
**Author:** Torben Nehmer
|
||||||
|
|||||||
+51
-21
@@ -1,10 +1,11 @@
|
|||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
Dieses Dokument richtet sich an Menschen. Es gibt drei Wege: ein **Release herunterladen**
|
Dieses Dokument richtet sich an Menschen. Es gibt vier Wege: ein **Release herunterladen**
|
||||||
(der normale Weg zu einer neuen Instanz), eine Distribution **selbst exportieren**, oder
|
(der normale Weg zu einer neuen Instanz), eine Distribution **selbst exportieren**, **dieses
|
||||||
**dieses Repo klonen** (Torbens persönliche Wiki, samt Inhalt). Der agent-seitige Ablauf steckt
|
Repo klonen** (Testbett und Demo, samt Beispielkorpus), oder eine **private Instanz mit diesem
|
||||||
in `instructions/`; hier stehen nur die menschlichen Teile - für die vollständige
|
Repo als Upstream** aufsetzen. Der agent-seitige Ablauf steckt in `instructions/`; hier stehen
|
||||||
Kommandoreferenz siehe [tools/CONTRACT.md](tools/CONTRACT.md).
|
nur die menschlichen Teile - für die vollständige Kommandoreferenz siehe
|
||||||
|
[tools/CONTRACT.md](tools/CONTRACT.md).
|
||||||
|
|
||||||
## Voraussetzungen
|
## Voraussetzungen
|
||||||
|
|
||||||
@@ -16,19 +17,22 @@ Kommandoreferenz siehe [tools/CONTRACT.md](tools/CONTRACT.md).
|
|||||||
## Weg A: Release herunterladen
|
## Weg A: Release herunterladen
|
||||||
|
|
||||||
Der kürzeste Weg zu einer eigenen Instanz - kein Checkout dieses Repos nötig. Jedes Release
|
Der kürzeste Weg zu einer eigenen Instanz - kein Checkout dieses Repos nötig. Jedes Release
|
||||||
trägt genau einen `dist export`-Baum plus eine Prüfsumme. Das Repo ist derzeit privat, der
|
trägt genau einen `dist export`-Baum plus eine Prüfsumme. Das Repo ist öffentlich, der Download
|
||||||
Download braucht also ein Gitea-Token mit Lesezugriff (siehe
|
braucht also weder Konto noch Token:
|
||||||
[Konfiguration](#konfiguration)):
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
BASE=https://gitea.nehmer.net/torben/chemenu/releases/download/v<version>
|
BASE=https://gitea.nehmer.net/torben/chemenu/releases/download/v<version>
|
||||||
curl -LO -H "Authorization: token $WIKITOOL_UPDATE_TOKEN" $BASE/chemenu-stack-<version>.tar.gz
|
curl -LO $BASE/chemenu-stack-<version>.tar.gz
|
||||||
curl -LO -H "Authorization: token $WIKITOOL_UPDATE_TOKEN" $BASE/chemenu-stack-<version>.tar.gz.sha256
|
curl -LO $BASE/chemenu-stack-<version>.tar.gz.sha256
|
||||||
sha256sum -c chemenu-stack-<version>.tar.gz.sha256
|
sha256sum -c chemenu-stack-<version>.tar.gz.sha256
|
||||||
tar xzf chemenu-stack-<version>.tar.gz
|
tar xzf chemenu-stack-<version>.tar.gz
|
||||||
cd chemenu-stack-<version>
|
cd chemenu-stack-<version>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Die Prüfsumme ist nicht Zierde: Sie ist das Einzige, was einen unterbrochenen Download von
|
||||||
|
einem vollständigen unterscheidet, und `sha256sum -c` muss `OK` sagen, bevor irgendetwas
|
||||||
|
entpackt wird.
|
||||||
|
|
||||||
Danach weiter mit Schritt 2 aus Weg B: den Agenten
|
Danach weiter mit Schritt 2 aus Weg B: den Agenten
|
||||||
[instructions/setup-instance.md](instructions/setup-instance.md) ausführen lassen. Der
|
[instructions/setup-instance.md](instructions/setup-instance.md) ausführen lassen. Der
|
||||||
entpackte Baum ist bereits eine Distribution - Schritt 1 (`dist export`) entfällt.
|
entpackte Baum ist bereits eine Distribution - Schritt 1 (`dist export`) entfällt.
|
||||||
@@ -82,10 +86,14 @@ Zwei Schritte, von denen nur der erste rein menschlich ist:
|
|||||||
|
|
||||||
## Weg C: Dieses Repo klonen
|
## Weg C: Dieses Repo klonen
|
||||||
|
|
||||||
Für Torbens Instanz selbst, oder einen Fork davon samt Inhalt:
|
Für die Arbeit am Stack selbst, oder um sich den mitgelieferten Korpus als begehbares Beispiel
|
||||||
|
anzusehen. Was hier liegt, ist ein **Testbett und eine Demo**, keine produktive Wissensbasis:
|
||||||
|
rund 170 Seiten, die den Stack selbst dokumentieren - Gates, Lint, Versionierung, Suche, das
|
||||||
|
Wiki-Muster. Wer eigenes Wissen sammeln will, nimmt Weg A oder B und fängt mit einem leeren
|
||||||
|
`kb/` an.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone <repo-url>
|
git clone https://gitea.nehmer.net/torben/chemenu.git
|
||||||
cd chemenu
|
cd chemenu
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -102,6 +110,21 @@ Checkout* beschreibt und nicht das Repo. Sie ist optional; wer sie anlegt, spart
|
|||||||
folgenden Session die Fragen nach Harness, MCP-Servern und Remote. Vorlage:
|
folgenden Session die Fragen nach Harness, MCP-Servern und Remote. Vorlage:
|
||||||
`ENVIRONMENT.md.template`, Ablauf: Schritt 5 in `instructions/bootstrap.md`.
|
`ENVIRONMENT.md.template`, Ablauf: Schritt 5 in `instructions/bootstrap.md`.
|
||||||
|
|
||||||
|
## Weg D: Private Instanz mit diesem Repo als Upstream
|
||||||
|
|
||||||
|
Die Kombination aus A und C: eine eigene, nicht öffentliche Instanz, die weiterhin
|
||||||
|
Stack-Updates von hier zieht - per `git merge` statt per Tarball, also mit echtem
|
||||||
|
Drei-Wege-Merge statt `cp -r`.
|
||||||
|
|
||||||
|
Das ist der Weg mit dem höchsten Einsatz, weil ein Checkout dann zwei Remotes hat und git beim
|
||||||
|
Push nicht unterscheidet, welcher welcher ist. Ein falsches `--remote` legt privaten Inhalt auf
|
||||||
|
ein öffentliches Repo, und ein Force-Push holt das nicht zurück - die Objekte bleiben per SHA
|
||||||
|
abrufbar, bis auf dem Server die Reflogs verfallen.
|
||||||
|
|
||||||
|
Dagegen gibt es das **Publish-Remote-Gate**, und die Anleitung setzt es an die Stelle, an der
|
||||||
|
es wirkt: *vor* dem ersten `publish`. Vollständiges Vorgehen:
|
||||||
|
[instructions/private-instance.md](instructions/private-instance.md).
|
||||||
|
|
||||||
## Version und Updates
|
## Version und Updates
|
||||||
|
|
||||||
Jede Instanz trägt die Version des **Stacks** (Werkzeuge, Typen, Instruktionen, Contracts) -
|
Jede Instanz trägt die Version des **Stacks** (Werkzeuge, Typen, Instruktionen, Contracts) -
|
||||||
@@ -199,22 +222,23 @@ behält Schema und Shape.
|
|||||||
| `WIKI_AUTHOR` | Override für den Autornamen neuer Source-Seiten | `git config user.name` - fehlt beides, bricht `new` mit `ERROR` ab |
|
| `WIKI_AUTHOR` | Override für den Autornamen neuer Source-Seiten | `git config user.name` - fehlt beides, bricht `new` mit `ERROR` ab |
|
||||||
| `WIKITOOL_SESSION_ID` | Scopt das Iteration-Budget-Gate auf eine Aufgabe statt auf ein Terminal | Parent-Process-ID (siehe [instructions/session-setup.md](instructions/session-setup.md)) |
|
| `WIKITOOL_SESSION_ID` | Scopt das Iteration-Budget-Gate auf eine Aufgabe statt auf ein Terminal | Parent-Process-ID (siehe [instructions/session-setup.md](instructions/session-setup.md)) |
|
||||||
| `WIKITOOL_UPDATE_URL` | Release-Feed, den `version check` abfragt | Wert aus `.wikitool-release.json`, sonst der Feed der Ursprungs-Instanz |
|
| `WIKITOOL_UPDATE_URL` | Release-Feed, den `version check` abfragt | Wert aus `.wikitool-release.json`, sonst der Feed der Ursprungs-Instanz |
|
||||||
| `WIKITOOL_UPDATE_TOKEN` | Gitea-Token für den Release-Feed | keiner - **aber das Ursprungs-Repo ist derzeit privat, also wird ein Token gebraucht** (siehe unten) |
|
| `WIKITOOL_UPDATE_TOKEN` | Gitea-Token für den Release-Feed | keiner - gegen `torben/chemenu` nicht nötig, nur für einen privaten Fork (siehe unten) |
|
||||||
|
|
||||||
**Privates Ursprungs-Repo.** `torben/chemenu` ist nicht öffentlich lesbar. Gitea
|
**Gegen das Ursprungs-Repo braucht es kein Token.** `torben/chemenu` ist öffentlich lesbar;
|
||||||
antwortet anonymen Aufrufern für ein unsichtbares Repo mit demselben `404` wie für ein gar
|
`version check` und der Download in Weg A funktionieren ohne Konfiguration.
|
||||||
nicht existierendes - ein fehlendes Release und ein fehlender Zugriff sehen also identisch aus.
|
|
||||||
Für `version check` (und für den Download in Weg A) braucht es deshalb ein Gitea-Token mit
|
**Für einen privaten Fork schon.** Wer den Stack in ein eigenes, nicht öffentliches Repo legt
|
||||||
Lesezugriff:
|
und `WIKITOOL_UPDATE_URL` auf dessen Feed zeigen lässt, stößt auf eine Eigenheit, die man
|
||||||
|
kennen sollte: Gitea antwortet anonymen Aufrufern für ein unsichtbares Repo mit demselben
|
||||||
|
`404` wie für ein gar nicht existierendes. Ein fehlendes Release und ein fehlender Zugriff
|
||||||
|
sehen dann identisch aus - „kein Update gefunden" wäre in dem Fall schlicht gelogen. Dagegen
|
||||||
|
hilft ein Gitea-Token mit Lesezugriff:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export WIKITOOL_UPDATE_TOKEN="<gitea-token>"
|
export WIKITOOL_UPDATE_TOKEN="<gitea-token>"
|
||||||
tools/wikitool version check
|
tools/wikitool version check
|
||||||
```
|
```
|
||||||
|
|
||||||
Wird das Repo öffentlich geschaltet, entfällt das Token ersatzlos - der Feed ist dann anonym
|
|
||||||
lesbar und `version check` funktioniert ohne Konfiguration.
|
|
||||||
|
|
||||||
## Verifikation
|
## Verifikation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -259,6 +283,12 @@ tools/wikitool instructions verify
|
|||||||
sondern die Aufforderung, die Ausgabe einem Menschen zu zeigen: sie enthält die vollständige
|
sondern die Aufforderung, die Ausgabe einem Menschen zu zeigen: sie enthält die vollständige
|
||||||
Dateiliste und die exakte `--confirm <token>`-Zeile, die nach Freigabe veröffentlicht.
|
Dateiliste und die exakte `--confirm <token>`-Zeile, die nach Freigabe veröffentlicht.
|
||||||
Details: [instructions/gates.md](instructions/gates.md).
|
Details: [instructions/gates.md](instructions/gates.md).
|
||||||
|
- **`publish` endet mit Exit-Code 42 (Publish-Remote-Gate)** - dieser Checkout hat eine
|
||||||
|
`.wikitool-remotes.json`, und das angesteuerte Remote steht nicht darin. Ebenfalls kein
|
||||||
|
Fehler: Die Ausgabe nennt die Push-URL, an die geschrieben würde, und die erlaubten. Anders
|
||||||
|
als beim Mass-Update-Gate gibt es hier **keinen Token und keine Flagge** - stimmt das Ziel
|
||||||
|
wirklich, trägt der Mensch dessen URL selbst in die Datei ein. Ein Agent, der die Datei
|
||||||
|
anfasst, um an der Verweigerung vorbeizukommen, öffnet ein Gate aus eigenem Antrieb.
|
||||||
- **Ich will am Tool-Stack selbst weiterarbeiten (nicht nur Wiki-Inhalt betreiben)** - eine neue
|
- **Ich will am Tool-Stack selbst weiterarbeiten (nicht nur Wiki-Inhalt betreiben)** - eine neue
|
||||||
Instanz hat dafür keinen Weg: `dist export` lässt `instructions/dev/` (Stack-Entwicklung,
|
Instanz hat dafür keinen Weg: `dist export` lässt `instructions/dev/` (Stack-Entwicklung,
|
||||||
inkl. der vendorten `commonplace/`-Wissensbasis) bewusst und dauerhaft weg, ohne
|
inkl. der vendorten `commonplace/`-Wissensbasis) bewusst und dauerhaft weg, ohne
|
||||||
|
|||||||
@@ -25,9 +25,24 @@ merge, so it cannot notice that the receiving instance changed a file, and it ha
|
|||||||
surface, so nobody learns when upstream and local both touched the same one. It overwrites
|
surface, so nobody learns when upstream and local both touched the same one. It overwrites
|
||||||
silently.
|
silently.
|
||||||
|
|
||||||
A clone gets all of that from git. The private `main` deletes the upstream's demo corpus once;
|
A clone gets all of that from git. Stack changes land as real merges, with real conflicts where
|
||||||
every later `git merge upstream/main` sees *deleted-in-ours, unmodified-in-theirs* and resolves
|
they conflict.
|
||||||
without asking. Stack changes land as real merges, with real conflicts where they conflict.
|
|
||||||
|
**What a plain `git merge` does *not* give you is protection from the upstream's content.** The
|
||||||
|
private `main` deletes the demo corpus once, but that deletion does not make later upstream
|
||||||
|
changes to those paths go away. Measured, not assumed:
|
||||||
|
|
||||||
|
| Upstream does | `git merge upstream/main` does |
|
||||||
|
|---|---|
|
||||||
|
| modifies a page you deleted | `CONFLICT (modify/delete)` - and **leaves the upstream version in your working tree**. Resolve it with `git add -A` and the demo page is back. |
|
||||||
|
| adds a new page | stages it **silently**. No conflict, no prompt, no mention. |
|
||||||
|
| deletes a page you also deleted | nothing. The only harmless case. |
|
||||||
|
|
||||||
|
The middle row is the one that matters, because nothing announces it. An upstream that ships a
|
||||||
|
demo corpus *and* uses it as a test bed will add pages, and each one arrives in your instance
|
||||||
|
and starts showing up in your `lint`, your `index`, your `search` and your `confidence decay`.
|
||||||
|
|
||||||
|
So the merge has to be scoped. That is the procedure below, and it is not optional.
|
||||||
|
|
||||||
## Steps
|
## Steps
|
||||||
|
|
||||||
@@ -87,15 +102,43 @@ without asking. Stack changes land as real merges, with real conflicts where the
|
|||||||
|
|
||||||
## Taking a stack update
|
## Taking a stack update
|
||||||
|
|
||||||
|
Take the machinery, never the content. The merge is held open, the content stages are forced
|
||||||
|
back to your own state, and only then does it close:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
BEFORE=$(git rev-parse HEAD)
|
||||||
git fetch upstream
|
git fetch upstream
|
||||||
git merge upstream/main
|
|
||||||
|
# --no-commit holds the merge open; it may report conflicts under kb/ or raw/,
|
||||||
|
# which the next three lines are about to make irrelevant.
|
||||||
|
git merge --no-commit --no-ff upstream/main || true
|
||||||
|
|
||||||
|
# Whatever the merge did to the content stages, undo it. HEAD is still your
|
||||||
|
# pre-merge commit while the merge is open, so this restores exactly your side.
|
||||||
|
git rm -rq --cached --ignore-unmatch kb raw
|
||||||
|
rm -rf kb raw
|
||||||
|
git checkout HEAD -- kb raw
|
||||||
|
|
||||||
|
git commit --no-edit
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Then **check that it worked**, rather than trusting that it did:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git diff --name-only $BEFORE HEAD -- kb raw # must print nothing
|
||||||
|
```
|
||||||
|
|
||||||
|
An empty result is the proof that the update touched machinery only. A non-empty one means a
|
||||||
|
path slipped through - inspect it before going further.
|
||||||
|
|
||||||
Then, as after any stack change: `doctor`, `docs verify`, `instructions verify`, `migrate status`,
|
Then, as after any stack change: `doctor`, `docs verify`, `instructions verify`, `migrate status`,
|
||||||
`lint`. A `migrate status` with outstanding links means the update crossed a compatibility
|
`lint`. A `migrate status` with outstanding links means the update crossed a compatibility
|
||||||
boundary - follow [migrate-corpus.md](migrate-corpus.md) before doing anything else.
|
boundary - follow [migrate-corpus.md](migrate-corpus.md) before doing anything else.
|
||||||
|
|
||||||
|
**Why not just `git merge upstream/main`?** Because of the table above: a page the upstream
|
||||||
|
*adds* arrives with no conflict and no message. You would find out when `lint` starts reporting
|
||||||
|
pages you never wrote - if you noticed at all.
|
||||||
|
|
||||||
## Where stack development happens
|
## Where stack development happens
|
||||||
|
|
||||||
**In the public repo, not here.** That is not a preference; the stack is built that way. The
|
**In the public repo, not here.** That is not a preference; the stack is built that way. The
|
||||||
@@ -110,9 +153,12 @@ merge above. Nothing is lost by the detour: the fix has to pass that CI either w
|
|||||||
|
|
||||||
## Decision points
|
## Decision points
|
||||||
|
|
||||||
- **Merge conflict in `kb/` or `raw/`?** Something changed the upstream's corpus after you cut
|
- **Merge conflict in `kb/` or `raw/`?** Expected, and already handled: the update procedure
|
||||||
it. Resolve as "keep deleted" - your instance's content is yours, and the upstream's demo
|
above overwrites those stages with your own afterwards, so the conflict resolves itself.
|
||||||
corpus has no business in it.
|
Never resolve one by hand with `git add -A` - that is exactly how the upstream version, which
|
||||||
|
git left sitting in your working tree, gets committed into your instance.
|
||||||
|
- **`git diff` after the merge shows something under `kb/` or `raw/`?** Stop. The scoping step
|
||||||
|
did not take. Do not publish; find out which path came through and where from.
|
||||||
- **Conflict in `tools/`, `types/` or `instructions/`?** You changed the stack locally, which
|
- **Conflict in `tools/`, `types/` or `instructions/`?** You changed the stack locally, which
|
||||||
step "Where stack development happens" says not to do. Take the upstream side and re-file the
|
step "Where stack development happens" says not to do. Take the upstream side and re-file the
|
||||||
change as an issue there.
|
change as an issue there.
|
||||||
|
|||||||
Reference in New Issue
Block a user