Status: vollständig umgesetzt. Maschinerie und Type-Split in 4.0.0 (177c7e9), Korpusmigration in fünf Einheiten bis 3f99d67, Abschluss in 4.1.0 (cfe925a). Alle Akzeptanzkriterien unten sind abgehakt. Ein Befund aus dem Lauf ist bewusst als eigene Entscheidung ausgelagert: #43.
Der Befund
#39 hat die Eigentumsfrage richtig beantwortet und den Mechanismus stehen lassen, der sie überhaupt schwer gemacht hat. Der Stack benutzte an drei Stellen Prosa als Identifier:
War Identifier
Ist Identifier
Prosa daneben
## Beziehungen (Überschriftentext)
Ein Marker
Die Überschrift, frei in der Instanzsprache
hängt ab von (Label im Body-Bullet)
Ein Enum-Wert in related:
— (entfallen)
Die Position des Fußnotenblocks
Ein Marker
—
{section.relationships} und PRE_CONVENTIONS_NAMES aus 3.0.0 waren die Kompensation dafür, nicht der Entwurf. Sie sind entfallen.
Der Preis des Matchens auf Prosa stand schon im Repo: der Docstring von provenance.split_cite_block erzählt, wie acht Seiten Inhalt verloren, weil eine toolgeführte Region per Heuristik statt per Begrenzer gefunden wurde.
Entscheidungen
Getroffen in der Sitzung vom 2026-09-02, im Anschluss an #39.
Kantenmodell: direktional
Die erzwungene Bidirektionalität (kb/CONTRACT.md: "Every page links to what it mentions, in both directions", xref add schrieb beide Seiten) ist entfallen.
Sie ist mit der per-Collection-Autorisierung logisch unverträglich: die läuft pro Quell→Ziel-Paarung und steht in der COLLECTION.md der Quelle. Schreibt xref add die Gegenkante mit, entsteht eine Kante, deren Quellcollection eine andere ist — deren Regeln der Autor weder liest noch überblickt. Entweder das Werkzeug schreibt unautorisierte Kanten, oder die Regel "die Quellcollection entscheidet" löst sich auf.
Der Navigationseinwand ("ich lande auf Hermes und will sehen, was davon abhängt") ist durch die Umstellung besser gelöst als vorher: related: trägt maschinenlesbare Label, also ist die eingehende Liste ableitbar — gerendert von wikitool links show, vollständig und ohne Pflege. Die erzwungene Gegenkante garantierte das nie, sie garantierte nur, dass jemand daran gedacht hat.
Nicht in diesem Paket: der Articulation-Test und der connect-Report-Workflow aus Commonplace. Die ändern, wie Ingest arbeitet, nicht wie Links gespeichert werden.
Label sind Enum-Werte, nicht lokalisiert
- **depends_on:** [[Hermes]] steht so in einer deutschen Seite. Keine Anzeigetabelle, kein Vollständigkeitscheck, keine dreizehn Zeilen Instanzdaten. Der Enum-Wert ist der Anzeigetext.
Umkehrbar: eine Anzeigetabelle bliebe jederzeit rein additiv nachrüstbar, weil der Enum-Wert der Identifier bleibt.
depends-on / required-by
depended-by ist nicht grammatisch — "depend" ist intransitiv und verlangt "on"; das Passiv wäre depended-on-by. Die etablierte Umkehrung der Domäne ist required-by (rpm; apt nennt es "Reverse Depends"). Beide erfüllen Commonplaces ADR-058-Invariante source <label> target.
Das bisherige benötigt ist entfallen — es war immer ein Synonym von hängt ab von.
Die Taxonomie
Sechs Register. Die ersten beiden sind neu und folgen dem Profil dieser Instanz (Infrastruktur, Architektur); die vier danach sind Commonplaces Katalog vollständig übernommen (alle 26).
35 Labels. Inverse nur, wo die Gegenrichtung eine eigene Primäraussage ist. Der Entwurf nannte zwei Paare; seit 4.1.0 sind es drei: depends-on/required-by, runs-on/hosts und composition/part-of. Alles andere hat keins — die eingehende Sicht wird gerendert. Ein Inversenpaar macht die Gegenkante nicht zur Pflicht; es legt fest, welches Label sie trägt, wenn jemand sie schreibt.
Wo was liegt:
instructions/link-taxonomy.md — der Katalog. Stack, Palette, bindet nichts.
kb/<name>/COLLECTION.mdoutbound: — die autorisierte Teilmenge pro Quell→Ziel-Paarung. Instanz, bindet. (Commonplace ADR-019)
lint prüft related: gegen diese Autorisierung.
Ein Label hinzufügen ist eine Zeile Daten, keine Codeänderung.
Zur Migrationsabbildung: der Entwurf behauptete eine 1:1-Abbildung von dreizehn Labeln. Gemessen am Korpus war das falsch — 152 distinkte Label in 337 Bullets, davon 102 mit genau einem Vorkommen, dazu 555 nackte ## Siehe auch-Bullets. Der Lauf war deshalb assisted und redaktionell, ohne mechanischen Boden; die Messung steht im Kommentar vom 2026-09-02.
Umsetzung
Reihenfolge, benannt statt nummeriert (Korrektur im ersten Kommentar): Abschnitt 1 → Abschnitt 4 → Abschnitte 2+3.
Zwei Achsen statt einer. migration_kind: beschreibt wie die Arbeit läuft, obligation:ob sie laufen muss:
Wert
Bedeutung
required (Default)
Der Inhalt muss in die neue Form, sonst passt er nicht zur Maschinerie
offered
Angebot: deine Datei funktioniert weiter, hier ist eine Verbesserung der ausgelieferten Vorgabe
chain() zählt nur required; offers() gibt den Rest. Korrektur gegen den Entwurf: Offers sind gegen das Applied-Ledger gebunden, nicht gegen kb_version — eine angenommene Offer bewegt kb_version gerade nicht, also kann die Version nichts darüber aussagen, und ein unbeteiligter Pflichtschritt hätte jede Offer stillschweigend verschwinden lassen. migrate done <offer> schreibt einen Ledger-Eintrag ohne Versionsbewegung und ohne Reihenfolgeregel.
kb_state.divergent_files() liest die sha256 pro Datei aus .wikitool-release.json und beantwortet "editiert oder nur empfangen". None statt [] ohne Stamp — ein Entwicklungsbaum hat keinen, und "nichts abgewichen" wäre dort eine erfundene Antwort.
4. types/ teilt sich entlang root: — [x] erledigt
Type-Spec
Gehört
entity, concept, source, comparison
Instanz — Auslieferung als .template, adoptiert per Rename
instruction, lint-report, type-spec
Stack
dist export schlüsselt die vier root: kb-Type-Specs samt ihrer .schema.yaml als .template um; abgeleitet aus types/, nicht gelistet. Damit ist die deutsche Prosa in den vier Page-Type-Specs korrekt statt Migrationsschuld.
Der Stack nennt sein Minimum: STACK_REQUIRED_TYPES = ("source",) — muss existieren, Schema muss raw_files verlangen. STACK_REQUIRED_COLLECTIONS als separate Liste ist weg; stack_required_collections() leitet sie aus dem base_dir des source-Typs ab. provenance._SOURCE_TITLE_PREFIX liest title_prefix: aus dem Type-Spec.
Die Vorbedingung hält:source_pages_by_raw_file, legacy_source_pages und duplicate_raw_file_owners fragen alle page.kind, also das name: des Type-Specs — nicht Verzeichnis oder Titelpräfix. Die Ankermenge blieb so klein wie beschrieben.
2+3. Label werden Enum, toolgeführte Blöcke werden generierte Regionen — [x] erledigt
Eine Korpusmigration, weil beide jede Seite anfassen.
Gelöscht, ersatzlos:tools/chemenu/sections.py, heading_re, der Alias-Mechanismus, PRE_CONVENTIONS_NAMES, cite_block_heading, provenance.__getattr__, die {section.*}-Template-Variablen und xrefs Abschnittssuche. Kein Überschriftentext liegt mehr in Python.
Neu:blocks.py (Marker-Regionen <!-- wikitool:links --> / <!-- wikitool:footnotes -->), links.py, instructions/link-taxonomy.md, outbound: in den COLLECTION.md, links show als abgeleitete Eingangssicht.
Vier Korrekturen, die die Umsetzung erzwungen hat:
sections: fällt nicht weg. Die Region rendert ihre Überschrift mit, braucht also einen Text. Was wegfällt, ist das Matching — der Wert ist Anzeige, nicht Identifier. Von drei Slots auf zwei: links und footnotes.
Es gibt keine see-also-Region. Sie war die Spiegelhälfte des bidirektionalen xref add; unter direktionalen Kanten ist see-also ein Label innerhalb der Links-Region.
index rebuild rendert die Eingangssicht nicht. Eine Backlink-Liste pro Seite in einen Shard zu schreiben, der ohnehin eine Seitentabelle ist, verdoppelt ihn. Stattdessen links show --page, budgetbefreit wie search. Dazu: das gerenderte Bullet ist ein Wikilink, also zeigt Obsidians Backlink-Panel die Eingangssicht ohnehin.
Der Orphan-Check meldet mehr. Direktionale Kanten heißen: eine Seite, die nur nach außen zeigt, ist tatsächlich unerreichbar. Er misst jetzt Erreichbarkeit statt "ist xref gelaufen".
5. Abschluss (4.1.0) — [x] erledigt
unlabelled_edges und unauthorised_labels sind harte Fehler, gebunden an kb_version statt an ein Release-Datum: unterhalb 4.0.0 beratend, ab 4.0.0 hart. Das ist der Unterschied zum Weg von legacy_citation_markers, der in einer späteren Version umgelegt wurde und jede Instanz mit offener Migration auf rotes Lint setzte. Das Ledger kann die Frage inzwischen beantworten, also tut es das.
Dazu die zwei Befunde des Migrationslaufs, die den Katalog betrafen: outbound: ist an das Type-Spec gebunden (docs verify prüft die Kombination), und composition/part-of ist das dritte Inversenpaar. Details im Abschlusskommentar.
Akzeptanzkriterien
Kein Überschriftentext in tools/ — sections.py und PRE_CONVENTIONS_NAMES existieren nicht mehr
related: trägt Label als Enum-Wert; lint prüft sie gegen die outbound:-Autorisierung der Quellcollection
xref add schreibt genau eine Kante; die eingehende Sicht ist gerendert, nicht gespeichert
Marker-Paar-Invariant in migrate verify
instructions/link-taxonomy.md enthält alle 26 Commonplace-Labels plus die neun operativen
Ein Label hinzufügen braucht keine Codeänderung
migrate status trennt required von offered und blockiert nie auf offered
migrate status meldet, welche instanzeigenen Dateien von den Stamp-Hashes abweichen
Die vier Page-Type-Specs sind nicht im Distributions-Dateiplan; nur ihre .template
docs verify prüft STACK_REQUIRED_TYPES
Version-Bump nach instructions/dev/version-parts.md; Grenzübertritt gesetzt (4.0.0), Migrationsdokument vorhanden
Kein Bullet trägt ein Label außerhalb des Katalogs, und lint erzwingt das (nachgereichtes Kriterium aus dem Kommentar vom 2026-09-02)
Vorgeschichte
Aus #39 heraus, Sitzung vom 2026-09-02. Verworfen wurden unterwegs: eine Slot-Tabelle im Type-Spec plus Rendering-Datei in der Instanz (Kompensation für eine Ownership-Grenze an der falschen Stelle), kb/TEMPLATES.md als eigene Instanzdatei (dito), und eine Anzeigetabelle für lokalisierte Label (unnötig, sobald der Enum-Wert selbst angezeigt wird).
Ausgelagert
#43 — dem Katalog fehlt ein Register für Urheberschaft. kind/decision, prio/waiting: der Korpus ist konsistent, alle betroffenen Kanten stehen auf see-also, und der Auslöser ist das nächste Auftreten.
> **Status: vollständig umgesetzt.** Maschinerie und Type-Split in `4.0.0` (`177c7e9`), Korpusmigration in fünf Einheiten bis `3f99d67`, Abschluss in `4.1.0` (`cfe925a`). Alle Akzeptanzkriterien unten sind abgehakt. Ein Befund aus dem Lauf ist bewusst als eigene Entscheidung ausgelagert: **#43**.
## Der Befund
#39 hat die Eigentumsfrage richtig beantwortet und den Mechanismus stehen lassen, der sie überhaupt schwer gemacht hat. Der Stack benutzte an drei Stellen **Prosa als Identifier**:
| War Identifier | Ist Identifier | Prosa daneben |
|---|---|---|
| `## Beziehungen` (Überschriftentext) | Ein Marker | Die Überschrift, frei in der Instanzsprache |
| `hängt ab von` (Label im Body-Bullet) | Ein Enum-Wert in `related:` | — (entfallen) |
| Die *Position* des Fußnotenblocks | Ein Marker | — |
`{section.relationships}` und `PRE_CONVENTIONS_NAMES` aus 3.0.0 waren die Kompensation dafür, nicht der Entwurf. Sie sind entfallen.
Der Preis des Matchens auf Prosa stand schon im Repo: der Docstring von `provenance.split_cite_block` erzählt, wie acht Seiten Inhalt verloren, weil eine toolgeführte Region per Heuristik statt per Begrenzer gefunden wurde.
## Entscheidungen
Getroffen in der Sitzung vom 2026-09-02, im Anschluss an #39.
### Kantenmodell: direktional
Die erzwungene Bidirektionalität (`kb/CONTRACT.md`: *"Every page links to what it mentions, in both directions"*, `xref add` schrieb beide Seiten) ist **entfallen**.
Sie ist mit der per-Collection-Autorisierung logisch unverträglich: die läuft pro Quell→Ziel-Paarung und steht in der `COLLECTION.md` der **Quelle**. Schreibt `xref add` die Gegenkante mit, entsteht eine Kante, deren Quellcollection eine andere ist — deren Regeln der Autor weder liest noch überblickt. Entweder das Werkzeug schreibt unautorisierte Kanten, oder die Regel "die Quellcollection entscheidet" löst sich auf.
Der Navigationseinwand ("ich lande auf Hermes und will sehen, was davon abhängt") ist durch die Umstellung besser gelöst als vorher: `related:` trägt maschinenlesbare Label, also ist die eingehende Liste ableitbar — gerendert von `wikitool links show`, vollständig und ohne Pflege. Die erzwungene Gegenkante garantierte das nie, sie garantierte nur, dass jemand daran gedacht hat.
**Nicht in diesem Paket:** der Articulation-Test und der `connect`-Report-Workflow aus Commonplace. Die ändern, wie Ingest arbeitet, nicht wie Links gespeichert werden.
### Label sind Enum-Werte, nicht lokalisiert
`- **depends_on:** [[Hermes]]` steht so in einer deutschen Seite. Keine Anzeigetabelle, kein Vollständigkeitscheck, keine dreizehn Zeilen Instanzdaten. Der Enum-Wert **ist** der Anzeigetext.
Umkehrbar: eine Anzeigetabelle bliebe jederzeit rein additiv nachrüstbar, weil der Enum-Wert der Identifier bleibt.
### `depends-on` / `required-by`
`depended-by` ist nicht grammatisch — "depend" ist intransitiv und verlangt "on"; das Passiv wäre `depended-on-by`. Die etablierte Umkehrung der Domäne ist `required-by` (rpm; apt nennt es "Reverse Depends"). Beide erfüllen Commonplaces ADR-058-Invariante `source <label> target`.
Das bisherige `benötigt` ist entfallen — es war immer ein Synonym von `hängt ab von`.
## Die Taxonomie
Sechs Register. Die ersten beiden sind neu und folgen dem Profil dieser Instanz (Infrastruktur, Architektur); die vier danach sind Commonplaces Katalog **vollständig** übernommen (alle 26).
| Register | Labels |
|---|---|
| **Operational** (Entity↔Entity) | `depends-on` · `required-by` · `runs-on` · `hosts` · `uses` · `produces` · `consumes` · `maintains` · `owns` |
| **Realization** (Concept↔Entity) | `implements` · `operationalized-from` · `mechanism` · `procedure` · `applies-when` · `operates-on` · `invokes` |
| **Conceptual** (Concept↔Concept) | `extends` · `grounds` · `rests-on` · `enables` · `precondition` · `exemplifies` · `abstracted-from` · `contrasts` · `compares-with` · `contradicts` · `composition` · `part-of` |
| **Lineage** | `supersedes` · `derived-from` · `adapted-from` · `defined-in` |
| **Evidence** | `evidenced-by` · `is-evidence-for` |
| **Universal** | `see-also` (letzte Instanz) |
35 Labels. Inverse nur, wo die Gegenrichtung eine **eigene Primäraussage** ist. Der Entwurf nannte zwei Paare; seit 4.1.0 sind es **drei**: `depends-on`/`required-by`, `runs-on`/`hosts` und `composition`/`part-of`. Alles andere hat keins — die eingehende Sicht wird gerendert. Ein Inversenpaar macht die Gegenkante nicht zur Pflicht; es legt fest, welches Label sie trägt, wenn jemand sie schreibt.
**Wo was liegt:**
- `instructions/link-taxonomy.md` — der Katalog. Stack, Palette, bindet nichts.
- `kb/<name>/COLLECTION.md` `outbound:` — die autorisierte Teilmenge **pro Quell→Ziel-Paarung**. Instanz, bindet. (Commonplace ADR-019)
- `lint` prüft `related:` gegen diese Autorisierung.
Ein Label hinzufügen ist eine Zeile Daten, keine Codeänderung.
**Zur Migrationsabbildung:** der Entwurf behauptete eine 1:1-Abbildung von dreizehn Labeln. Gemessen am Korpus war das falsch — 152 distinkte Label in 337 Bullets, davon 102 mit genau einem Vorkommen, dazu 555 nackte `## Siehe auch`-Bullets. Der Lauf war deshalb `assisted` und redaktionell, ohne mechanischen Boden; die Messung steht im Kommentar vom 2026-09-02.
## Umsetzung
Reihenfolge, benannt statt nummeriert (Korrektur im ersten Kommentar): **Abschnitt 1 → Abschnitt 4 → Abschnitte 2+3.**
### 1. Angebotene Migrationen (`obligation:`) — [x] erledigt
Zwei Achsen statt einer. `migration_kind:` beschreibt *wie* die Arbeit läuft, `obligation:` *ob* sie laufen muss:
| Wert | Bedeutung |
|---|---|
| `required` (Default) | Der Inhalt **muss** in die neue Form, sonst passt er nicht zur Maschinerie |
| `offered` | Angebot: deine Datei funktioniert weiter, hier ist eine Verbesserung der ausgelieferten Vorgabe |
`chain()` zählt nur `required`; `offers()` gibt den Rest. **Korrektur gegen den Entwurf:** Offers sind gegen das **Applied-Ledger** gebunden, nicht gegen `kb_version` — eine angenommene Offer bewegt `kb_version` gerade nicht, also kann die Version nichts darüber aussagen, und ein unbeteiligter Pflichtschritt hätte jede Offer stillschweigend verschwinden lassen. `migrate done <offer>` schreibt einen Ledger-Eintrag ohne Versionsbewegung und ohne Reihenfolgeregel.
`kb_state.divergent_files()` liest die sha256 pro Datei aus `.wikitool-release.json` und beantwortet "editiert oder nur empfangen". `None` statt `[]` ohne Stamp — ein Entwicklungsbaum hat keinen, und "nichts abgewichen" wäre dort eine erfundene Antwort.
### 4. `types/` teilt sich entlang `root:` — [x] erledigt
| Type-Spec | Gehört |
|---|---|
| `entity`, `concept`, `source`, `comparison` | **Instanz** — Auslieferung als `.template`, adoptiert per Rename |
| `instruction`, `lint-report`, `type-spec` | Stack |
`dist export` schlüsselt die vier `root: kb`-Type-Specs **samt ihrer `.schema.yaml`** als `.template` um; abgeleitet aus `types/`, nicht gelistet. Damit ist die deutsche Prosa in den vier Page-Type-Specs **korrekt** statt Migrationsschuld.
Der Stack nennt sein Minimum: `STACK_REQUIRED_TYPES = ("source",)` — muss existieren, Schema muss `raw_files` verlangen. `STACK_REQUIRED_COLLECTIONS` als separate Liste ist **weg**; `stack_required_collections()` leitet sie aus dem `base_dir` des `source`-Typs ab. `provenance._SOURCE_TITLE_PREFIX` liest `title_prefix:` aus dem Type-Spec.
**Die Vorbedingung hält:** `source_pages_by_raw_file`, `legacy_source_pages` und `duplicate_raw_file_owners` fragen alle `page.kind`, also das `name:` des Type-Specs — nicht Verzeichnis oder Titelpräfix. Die Ankermenge blieb so klein wie beschrieben.
### 2+3. Label werden Enum, toolgeführte Blöcke werden generierte Regionen — [x] erledigt
Eine Korpusmigration, weil beide jede Seite anfassen.
**Gelöscht, ersatzlos:** `tools/chemenu/sections.py`, `heading_re`, der Alias-Mechanismus, `PRE_CONVENTIONS_NAMES`, `cite_block_heading`, `provenance.__getattr__`, die `{section.*}`-Template-Variablen und `xref`s Abschnittssuche. Kein Überschriftentext liegt mehr in Python.
**Neu:** `blocks.py` (Marker-Regionen `<!-- wikitool:links -->` / `<!-- wikitool:footnotes -->`), `links.py`, `instructions/link-taxonomy.md`, `outbound:` in den `COLLECTION.md`, `links show` als abgeleitete Eingangssicht.
**Vier Korrekturen, die die Umsetzung erzwungen hat:**
1. **`sections:` fällt nicht weg.** Die Region rendert ihre Überschrift mit, braucht also einen Text. Was wegfällt, ist das *Matching* — der Wert ist Anzeige, nicht Identifier. Von drei Slots auf zwei: `links` und `footnotes`.
2. **Es gibt keine `see-also`-Region.** Sie war die Spiegelhälfte des bidirektionalen `xref add`; unter direktionalen Kanten ist `see-also` ein *Label* innerhalb der Links-Region.
3. **`index rebuild` rendert die Eingangssicht nicht.** Eine Backlink-Liste pro Seite in einen Shard zu schreiben, der ohnehin eine Seitentabelle ist, verdoppelt ihn. Stattdessen `links show --page`, budgetbefreit wie `search`. Dazu: das gerenderte Bullet **ist** ein Wikilink, also zeigt Obsidians Backlink-Panel die Eingangssicht ohnehin.
4. **Der Orphan-Check meldet mehr.** Direktionale Kanten heißen: eine Seite, die nur nach außen zeigt, ist tatsächlich unerreichbar. Er misst jetzt Erreichbarkeit statt "ist `xref` gelaufen".
### 5. Abschluss (4.1.0) — [x] erledigt
`unlabelled_edges` und `unauthorised_labels` sind harte Fehler, **gebunden an `kb_version`** statt an ein Release-Datum: unterhalb 4.0.0 beratend, ab 4.0.0 hart. Das ist der Unterschied zum Weg von `legacy_citation_markers`, der in einer späteren Version umgelegt wurde und jede Instanz mit offener Migration auf rotes Lint setzte. Das Ledger kann die Frage inzwischen beantworten, also tut es das.
Dazu die zwei Befunde des Migrationslaufs, die den Katalog betrafen: `outbound:` ist an das Type-Spec gebunden (`docs verify` prüft die Kombination), und `composition`/`part-of` ist das dritte Inversenpaar. Details im Abschlusskommentar.
## Akzeptanzkriterien
- [x] Kein Überschriftentext in `tools/` — `sections.py` und `PRE_CONVENTIONS_NAMES` existieren nicht mehr
- [x] `related:` trägt Label als Enum-Wert; `lint` prüft sie gegen die `outbound:`-Autorisierung der Quellcollection
- [x] `xref add` schreibt genau eine Kante; die eingehende Sicht ist gerendert, nicht gespeichert
- [x] Marker-Paar-Invariant in `migrate verify`
- [x] `instructions/link-taxonomy.md` enthält alle 26 Commonplace-Labels plus die neun operativen
- [x] Ein Label hinzufügen braucht keine Codeänderung
- [x] `migrate status` trennt `required` von `offered` und blockiert nie auf `offered`
- [x] `migrate status` meldet, welche instanzeigenen Dateien von den Stamp-Hashes abweichen
- [x] Die vier Page-Type-Specs sind nicht im Distributions-Dateiplan; nur ihre `.template`
- [x] `docs verify` prüft `STACK_REQUIRED_TYPES`
- [x] Version-Bump nach `instructions/dev/version-parts.md`; Grenzübertritt gesetzt (`4.0.0`), Migrationsdokument vorhanden
- [x] Kein Bullet trägt ein Label außerhalb des Katalogs, und `lint` erzwingt das (nachgereichtes Kriterium aus dem Kommentar vom 2026-09-02)
## Vorgeschichte
Aus #39 heraus, Sitzung vom 2026-09-02. Verworfen wurden unterwegs: eine Slot-Tabelle im Type-Spec plus Rendering-Datei in der Instanz (Kompensation für eine Ownership-Grenze an der falschen Stelle), `kb/TEMPLATES.md` als eigene Instanzdatei (dito), und eine Anzeigetabelle für lokalisierte Label (unnötig, sobald der Enum-Wert selbst angezeigt wird).
## Ausgelagert
**#43** — dem Katalog fehlt ein Register für Urheberschaft. `kind/decision`, `prio/waiting`: der Korpus ist konsistent, alle betroffenen Kanten stehen auf `see-also`, und der Auslöser ist das nächste Auftreten.
Korrektur zur Reihenfolge. Der Satz "Reihenfolge: 4 vor 3" unter Umsetzung stammt aus der Nummerierung des Vorgesprächs und widerspricht der Nummerierung dieses Issues. Gemeint ist, benannt statt nummeriert:
Angebotene Migrationen (obligation:) — zuerst, weil sie der Träger für alles Folgende sind.
types/ teilt sich entlang root: — die Übergabe an die Instanz ist ohne diesen Träger unumkehrbar.
Label werden Enum, Kanten direktional ⟂ toolgeführte Blöcke werden generiert — gemeinsam, als eine Korpus-Migration, weil beide jede Seite anfassen.
Also: Abschnitt 1 → Abschnitt 4 → Abschnitte 2+3.
**Korrektur zur Reihenfolge.** Der Satz "Reihenfolge: 4 vor 3" unter *Umsetzung* stammt aus der Nummerierung des Vorgesprächs und widerspricht der Nummerierung dieses Issues. Gemeint ist, benannt statt nummeriert:
1. **Angebotene Migrationen** (`obligation:`) — zuerst, weil sie der Träger für alles Folgende sind.
2. **`types/` teilt sich entlang `root:`** — die Übergabe an die Instanz ist ohne diesen Träger unumkehrbar.
3. **Label werden Enum, Kanten direktional** ⟂ **toolgeführte Blöcke werden generiert** — gemeinsam, als *eine* Korpus-Migration, weil beide jede Seite anfassen.
Also: Abschnitt 1 → Abschnitt 4 → Abschnitte 2+3.
Noch nicht publiziert, VERSION noch auf 3.0.0 — der Bump kommt am Ende des Pakets.
Vorbedingung geprüft, sie hält
Die im Issue offene Frage — löst der Provenance-Pfad über den Typ oder über Pfad/Präfix auf? — ist beantwortet: source_pages_by_raw_file, legacy_source_pages und duplicate_raw_file_owners fragen alle page.kind != "source", also das name: des Type-Specs. Die einzige Kopplung an eine Zeichenkette war provenance._SOURCE_TITLE_PREFIX, und die ist jetzt aufgelöst (liest title_prefix: aus dem Type-Spec, mit dem alten Literal als Fallback für einen Baum ohne auflösbaren source-Typ — sonst würden bestehende Cite-IDs wandern).
Die Ankermenge bleibt also so klein wie im Issue beschrieben.
Abschnitt 1 — obligation:
required | offered, Default required. chain() zählt nur required; offers() gibt den Rest.
Eine Lücke, die ich beim Testen gefunden habe und die im Issue noch falsch stand: ich hatte Offers gegen kb_version gefiltert. Das ist falsch — eine angenommene Offer bewegt kb_version gerade nicht, also kann die Version nichts darüber aussagen, ob sie angenommen wurde, und ein unbeteiligter Pflicht-Schritt hätte jede Offer stillschweigend verschwinden lassen. Offers sind jetzt gegen das Applied-Ledger gebunden, und migrate done <offer> schreibt einen Eintrag dorthin, ohne die Version zu bewegen und ohne Reihenfolgeregel — eine Offer ist kein Glied der Kette, also gibt es nichts zu überspringen.
Die Erkennungshälfte ist aktiviert: kb_state.divergent_files() liest die sha256 pro Datei aus .wikitool-release.json und beantwortet „editiert oder nur empfangen". None statt [] ohne Stamp — ein Entwicklungsbaum hat keinen, und „nichts abgewichen" wäre dort eine erfundene Antwort.
Abschnitt 4 — types/ entlang root:
dist export schlüsselt die vier root: kb-Type-Specs samt ihrer .schema.yaml als .template um; instruction, lint-report und type-spec gehen verbatim. Abgeleitet aus types/, nicht gelistet — ein eigener Page-Typ der Instanz bekommt dieselbe Behandlung ohne Codeänderung. find_leaks verweigert die gefüllten Namen.
STACK_REQUIRED_COLLECTIONS ist als separate Liste weg: stack_required_collections() leitet sie aus dem base_dir des source-Typs ab. Eine Instanz, die kb/sources/ umbenennt und das im Type-Spec sagt, bleibt konsistent statt gegen eine hartkodierte Zeichenkette zu laufen.
docs verify prüft neu STACK_REQUIRED_TYPES: es muss einen Type-Spec name: source geben, dessen Schema raw_files fordert. Mehr nicht.
Damit ist die deutsche Prosa in den vier Page-Type-Specs erledigt — kein Übersetzungsschritt, keine Migrationsschuld. Verifiziert am vollen Replay: Export → frische englische Instanz → doctor/docs verify grün, new entity scaffoldet ## Relationships / ## See Also aus der eigenen kb/CONVENTIONS.md, und die ## Beschreibung-Überschriften stehen in einer Datei, die dieser Instanz gehört und die sie übersetzen darf.
838 Tests grün.
Als nächstes
Abschnitte 2+3 als eine Korpus-Migration: Label werden Enum, Kanten direktional, toolgeführte Blöcke werden generierte Regionen mit Markern.
## Zwischenstand: Abschnitte 1 und 4 stehen
Noch nicht publiziert, `VERSION` noch auf `3.0.0` — der Bump kommt am Ende des Pakets.
### Vorbedingung geprüft, sie hält
Die im Issue offene Frage — löst der Provenance-Pfad über den Typ oder über Pfad/Präfix auf? — ist beantwortet: `source_pages_by_raw_file`, `legacy_source_pages` und `duplicate_raw_file_owners` fragen alle `page.kind != "source"`, also das `name:` des Type-Specs. Die einzige Kopplung an eine Zeichenkette war `provenance._SOURCE_TITLE_PREFIX`, und die ist jetzt aufgelöst (liest `title_prefix:` aus dem Type-Spec, mit dem alten Literal als Fallback für einen Baum ohne auflösbaren `source`-Typ — sonst würden bestehende Cite-IDs wandern).
Die Ankermenge bleibt also so klein wie im Issue beschrieben.
### Abschnitt 1 — `obligation:`
`required` | `offered`, Default `required`. `chain()` zählt nur `required`; `offers()` gibt den Rest.
**Eine Lücke, die ich beim Testen gefunden habe und die im Issue noch falsch stand:** ich hatte Offers gegen `kb_version` gefiltert. Das ist falsch — eine angenommene Offer bewegt `kb_version` gerade *nicht*, also kann die Version nichts darüber aussagen, ob sie angenommen wurde, und ein unbeteiligter Pflicht-Schritt hätte jede Offer stillschweigend verschwinden lassen. Offers sind jetzt gegen das **Applied-Ledger** gebunden, und `migrate done <offer>` schreibt einen Eintrag dorthin, ohne die Version zu bewegen und ohne Reihenfolgeregel — eine Offer ist kein Glied der Kette, also gibt es nichts zu überspringen.
Die Erkennungshälfte ist aktiviert: `kb_state.divergent_files()` liest die sha256 pro Datei aus `.wikitool-release.json` und beantwortet „editiert oder nur empfangen". `None` statt `[]` ohne Stamp — ein Entwicklungsbaum hat keinen, und „nichts abgewichen" wäre dort eine erfundene Antwort.
### Abschnitt 4 — `types/` entlang `root:`
`dist export` schlüsselt die vier `root: kb`-Type-Specs **samt ihrer `.schema.yaml`** als `.template` um; `instruction`, `lint-report` und `type-spec` gehen verbatim. Abgeleitet aus `types/`, nicht gelistet — ein eigener Page-Typ der Instanz bekommt dieselbe Behandlung ohne Codeänderung. `find_leaks` verweigert die gefüllten Namen.
`STACK_REQUIRED_COLLECTIONS` ist als separate Liste **weg**: `stack_required_collections()` leitet sie aus dem `base_dir` des `source`-Typs ab. Eine Instanz, die `kb/sources/` umbenennt und das im Type-Spec sagt, bleibt konsistent statt gegen eine hartkodierte Zeichenkette zu laufen.
`docs verify` prüft neu `STACK_REQUIRED_TYPES`: es muss einen Type-Spec `name: source` geben, dessen Schema `raw_files` fordert. Mehr nicht.
**Damit ist die deutsche Prosa in den vier Page-Type-Specs erledigt** — kein Übersetzungsschritt, keine Migrationsschuld. Verifiziert am vollen Replay: Export → frische englische Instanz → `doctor`/`docs verify` grün, `new entity` scaffoldet `## Relationships` / `## See Also` aus der eigenen `kb/CONVENTIONS.md`, und die `## Beschreibung`-Überschriften stehen in einer Datei, die dieser Instanz gehört und die sie übersetzen darf.
838 Tests grün.
### Als nächstes
Abschnitte 2+3 als eine Korpus-Migration: Label werden Enum, Kanten direktional, toolgeführte Blöcke werden generierte Regionen mit Markern.
Korrektur: die Migrationsabbildung im Issue-Text stimmt nicht
Der Abschnitt Die Taxonomie behauptet eine Abbildung „1:1 bis auf eine Zusammenlegung" von dreizehn Labeln. Gemessen am Korpus ist das falsch, und zwar deutlich.
152 distinkte Beziehungslabel in 337 Bullets.kb/CONVENTIONS.md listet dreizehn; nichts hat je geprüft, ob sie eingehalten werden, und sie wurden es nicht. Die Top 20 decken 167 der 337 Bullets ab, 102 Label kommen genau einmal vor (Erfand, Gegenmittel zu, spiegelt das gleiche Muster wie, würde implementiert von, …). Die dokumentierte Liste deckt grob ein Sechstel.
Dazu ein Anteil Gegenrichtungen, den ich unterschätzt hatte: Verwendet von (20×), umgesetzt von, implementiert durch, Ersetzt durch, Erweitert durch. Unter dem direktionalen Modell sind das genau die Kanten, die nicht mehr gespeichert, sondern gerendert werden.
Und ## Siehe auch
555 nackte - [[X]]-Bullets ohne Label — mehr als alle gelabelten Beziehungen zusammen. Die Spiegelhälfte der erzwungenen Bidirektionalität.
Siehe-auch-Kanten gesamt
555
durch eine gelabelte Kante gedeckt — beweisbar redundant
353
beim ersatzlosen Streichen die einzige Verbindung
202
betroffene Zielseiten
63
Die 353 fallen mechanisch. Bei den 202 ist mechanisch nicht entscheidbar, ob jemand die Verbindung gemeint hat oder ob xref add sie als Spiegel schrieb und die gelabelte Hälfte später verloren ging.
Folge
migration_kind ist assisted, nicht mechanical. Umfang: ~539 Einzelentscheidungen (337 gelabelte Bullets + 202 ungedeckte Siehe-auch-Kanten) über rund 140 Seiten, nach migrate-corpus.md also drei bis vier Einheiten mit je eigenem Publish.
Entschieden: redaktionell, nicht mit mechanischem Boden. Die Alternative wäre gewesen, die Top 20 per Tabelle abzubilden und den Rest auf see-also zu setzen — das hätte ~370 von ~539 Kanten mit dem schwächsten Label des Katalogs starten lassen, also genau den verwandt mit-Bodensatz reproduziert, den die Umstellung beendet, nur diesmal als dokumentierten Startzustand.
Das Akzeptanzkriterium „Ein Label hinzufügen braucht keine Codeänderung" bleibt; neu dazu: die Migration ist erst fertig, wenn kein Bullet mehr ein Label außerhalb des Katalogs trägt und lint das erzwingt.
Die Mechanik ist davon nicht blockiert und wird zuerst gebaut.
## Korrektur: die Migrationsabbildung im Issue-Text stimmt nicht
Der Abschnitt *Die Taxonomie* behauptet eine Abbildung „1:1 bis auf eine Zusammenlegung" von dreizehn Labeln. Gemessen am Korpus ist das falsch, und zwar deutlich.
**152 distinkte Beziehungslabel in 337 Bullets.** `kb/CONVENTIONS.md` listet dreizehn; nichts hat je geprüft, ob sie eingehalten werden, und sie wurden es nicht. Die Top 20 decken 167 der 337 Bullets ab, **102 Label kommen genau einmal vor** (`Erfand`, `Gegenmittel zu`, `spiegelt das gleiche Muster wie`, `würde implementiert von`, …). Die dokumentierte Liste deckt grob ein Sechstel.
Dazu ein Anteil **Gegenrichtungen**, den ich unterschätzt hatte: `Verwendet von` (20×), `umgesetzt von`, `implementiert durch`, `Ersetzt durch`, `Erweitert durch`. Unter dem direktionalen Modell sind das genau die Kanten, die nicht mehr gespeichert, sondern gerendert werden.
### Und `## Siehe auch`
555 nackte `- [[X]]`-Bullets ohne Label — mehr als alle gelabelten Beziehungen zusammen. Die Spiegelhälfte der erzwungenen Bidirektionalität.
| | |
|---|---|
| Siehe-auch-Kanten gesamt | 555 |
| durch eine gelabelte Kante gedeckt — beweisbar redundant | **353** |
| beim ersatzlosen Streichen die einzige Verbindung | **202** |
| betroffene Zielseiten | 63 |
Die 353 fallen mechanisch. Bei den 202 ist mechanisch nicht entscheidbar, ob jemand die Verbindung gemeint hat oder ob `xref add` sie als Spiegel schrieb und die gelabelte Hälfte später verloren ging.
### Folge
`migration_kind` ist **`assisted`**, nicht `mechanical`. Umfang: ~539 Einzelentscheidungen (337 gelabelte Bullets + 202 ungedeckte Siehe-auch-Kanten) über rund 140 Seiten, nach `migrate-corpus.md` also drei bis vier Einheiten mit je eigenem Publish.
**Entschieden: redaktionell, nicht mit mechanischem Boden.** Die Alternative wäre gewesen, die Top 20 per Tabelle abzubilden und den Rest auf `see-also` zu setzen — das hätte ~370 von ~539 Kanten mit dem schwächsten Label des Katalogs starten lassen, also genau den `verwandt mit`-Bodensatz reproduziert, den die Umstellung beendet, nur diesmal als dokumentierten Startzustand.
Das Akzeptanzkriterium *„Ein Label hinzufügen braucht keine Codeänderung"* bleibt; neu dazu: **die Migration ist erst fertig, wenn kein Bullet mehr ein Label außerhalb des Katalogs trägt** und `lint` das erzwingt.
Die Mechanik ist davon nicht blockiert und wird zuerst gebaut.
u0 steht — die Mechanik ist vollständig, keine Seite angefasst
856 Tests grün, docs verify / instructions verify / doctor grün, keine Hard-Error-Findings im Korpus.
Gelöscht, ersatzlos
tools/chemenu/sections.py existiert nicht mehr. Mit ihm: heading_re, der Alias-Mechanismus, PRE_CONVENTIONS_NAMES, cite_block_heading, provenance.__getattr__, die {section.*}-Template-Variablen und xrefs Abschnittssuche (_section_bounds, add_bullet_to_section, add_relationship_bullet, add_see_also_bullet). Kein Überschriftentext liegt mehr in Python — bis auf zwei Fallback-Strings in conventions.DEFAULT_HEADINGS, und die sind rein kosmetisch: der Marker trägt die Identität der Region, also rendert ein falscher Default falsche Wörter statt Struktur zu zerlegen, und der nächste Write repariert es.
Neu
blocks.py (Marker-Regionen: <!-- wikitool:links --> / <!-- wikitool:footnotes -->), links.py (gelabelte Kanten, liest beide Formen), instructions/link-taxonomy.md (35 Label), outbound: in allen vier COLLECTION.md, links show als abgeleitete Eingangssicht.
Vier Korrekturen am Entwurf, die die Umsetzung erzwungen hat
1. sections: fällt nicht weg. Das Issue behauptet es. Falsch: die Region rendert ihre Überschrift mit, also braucht sie einen Text. Was wegfällt, ist das Matching — der Wert ist jetzt Anzeige, nicht Identifier, dieselbe Kategorie wie layout: {project: {title: Projekte}}. Von drei Slots auf zwei: links und footnotes.
2. Es gibt keine see-also-Region mehr. Sie war die Spiegelhälfte des bidirektionalen xref add; unter direktionalen Kanten ist see-also ein Label innerhalb der Links-Region. Zwei Regionen statt drei.
3. index rebuild rendert die Eingangssicht nicht. Ich hatte das so ins Issue geschrieben und es beim Bauen verworfen: eine Backlink-Liste pro Seite in einen Shard zu schreiben, der ohnehin eine Seitentabelle ist, verdoppelt ihn und hilft niemandem. Stattdessen wikitool links show --page — Ausgangskanten aus related:, Eingangskanten über den Korpus berechnet, budgetbefreit wie search. Dazu der Punkt, den ich vorher übersehen hatte: das gerenderte Bullet - **depends-on:** [[Hermes]]ist ein Wikilink, also zeigt Obsidians Backlink-Panel die Eingangssicht für einen menschlichen Leser bereits von selbst.
4. Der Orphan-Check meldet jetzt mehr. Direktionale Kanten heißen: eine Seite, die nur nach außen zeigt, ist tatsächlich unerreichbar. Das ist die Prüfung, die ihre Arbeit tut — sie misst Erreichbarkeit statt "ist xref gelaufen" —, aber es ist eine Verhaltensänderung, die u1–u4 sichtbar machen werden.
Lint-Schärfe
malformed_edges und unbalanced_markers sind hart ab sofort: keines beschreibt eine unkonvertierte Seite, nur eine kaputte. unlabelled_edges und unauthorised_labels sind advisory bis zum Abschluss der Migration — sie beschreiben exakt das Fenster, für das .wikitool-kb.json existiert. Die Beförderung zu Hard Errors ist Teil von u5, derselbe Weg, den legacy_citation_markers genommen hat.
480, nicht 337.related: trägt mehr Einträge als der Body gelabelte Bullets hat — Frontmatter und Body waren unter dem alten Modell längst auseinandergelaufen, und niemand konnte es sehen, solange das Label nur in der Prosa stand. Das ist selbst ein Befund.
Plan, Messwerte und die getroffenen Entscheidungen liegen in work/link-taxonomy-migration/. Noch nicht publiziert; VERSION steht weiter auf 3.0.0, der Bump gehört nach u5.
## u0 steht — die Mechanik ist vollständig, keine Seite angefasst
856 Tests grün, `docs verify` / `instructions verify` / `doctor` grün, keine Hard-Error-Findings im Korpus.
### Gelöscht, ersatzlos
`tools/chemenu/sections.py` existiert nicht mehr. Mit ihm: `heading_re`, der Alias-Mechanismus, `PRE_CONVENTIONS_NAMES`, `cite_block_heading`, `provenance.__getattr__`, die `{section.*}`-Template-Variablen und `xref`s Abschnittssuche (`_section_bounds`, `add_bullet_to_section`, `add_relationship_bullet`, `add_see_also_bullet`). **Kein Überschriftentext liegt mehr in Python** — bis auf zwei Fallback-Strings in `conventions.DEFAULT_HEADINGS`, und die sind rein kosmetisch: der Marker trägt die Identität der Region, also rendert ein falscher Default falsche Wörter statt Struktur zu zerlegen, und der nächste Write repariert es.
### Neu
`blocks.py` (Marker-Regionen: `<!-- wikitool:links -->` / `<!-- wikitool:footnotes -->`), `links.py` (gelabelte Kanten, liest beide Formen), `instructions/link-taxonomy.md` (35 Label), `outbound:` in allen vier `COLLECTION.md`, `links show` als abgeleitete Eingangssicht.
### Vier Korrekturen am Entwurf, die die Umsetzung erzwungen hat
**1. `sections:` fällt nicht weg.** Das Issue behauptet es. Falsch: die Region rendert ihre Überschrift *mit*, also braucht sie einen Text. Was wegfällt, ist das *Matching* — der Wert ist jetzt Anzeige, nicht Identifier, dieselbe Kategorie wie `layout: {project: {title: Projekte}}`. Von drei Slots auf zwei: `links` und `footnotes`.
**2. Es gibt keine `see-also`-Region mehr.** Sie war die Spiegelhälfte des bidirektionalen `xref add`; unter direktionalen Kanten ist `see-also` ein *Label* innerhalb der Links-Region. Zwei Regionen statt drei.
**3. `index rebuild` rendert die Eingangssicht nicht.** Ich hatte das so ins Issue geschrieben und es beim Bauen verworfen: eine Backlink-Liste pro Seite in einen Shard zu schreiben, der ohnehin eine Seitentabelle ist, verdoppelt ihn und hilft niemandem. Stattdessen `wikitool links show --page` — Ausgangskanten aus `related:`, Eingangskanten über den Korpus berechnet, budgetbefreit wie `search`. Dazu der Punkt, den ich vorher übersehen hatte: das gerenderte Bullet `- **depends-on:** [[Hermes]]` **ist** ein Wikilink, also zeigt Obsidians Backlink-Panel die Eingangssicht für einen menschlichen Leser bereits von selbst.
**4. Der Orphan-Check meldet jetzt mehr.** Direktionale Kanten heißen: eine Seite, die nur nach außen zeigt, ist tatsächlich unerreichbar. Das ist die Prüfung, die ihre Arbeit tut — sie misst Erreichbarkeit statt "ist `xref` gelaufen" —, aber es ist eine Verhaltensänderung, die u1–u4 sichtbar machen werden.
### Lint-Schärfe
`malformed_edges` und `unbalanced_markers` sind **hart** ab sofort: keines beschreibt eine unkonvertierte Seite, nur eine kaputte. `unlabelled_edges` und `unauthorised_labels` sind **advisory bis zum Abschluss der Migration** — sie beschreiben exakt das Fenster, für das `.wikitool-kb.json` existiert. Die Beförderung zu Hard Errors ist Teil von u5, derselbe Weg, den `legacy_citation_markers` genommen hat.
### Die Zahl für u1–u4
```
unlabelled_edges 480
unauthorised_labels 0
malformed_edges 0
unbalanced_markers 0
```
**480, nicht 337.** `related:` trägt mehr Einträge als der Body gelabelte Bullets hat — Frontmatter und Body waren unter dem alten Modell längst auseinandergelaufen, und niemand konnte es sehen, solange das Label nur in der Prosa stand. Das ist selbst ein Befund.
Plan, Messwerte und die getroffenen Entscheidungen liegen in `work/link-taxonomy-migration/`. Noch nicht publiziert; `VERSION` steht weiter auf `3.0.0`, der Bump gehört nach u5.
56 Dateien, +2.695/-753. VERSION auf 4.0.0, Migrationsdokument instructions/migrations/4.0.0-link-taxonomy.md (assisted, required), Baum sauber.
Dieser Commit ist die Baseline für u1–u4.migrate verify --from 177c7e9 vergleicht gegen einen Stand, in dem die Marker-Maschinerie existiert und noch keine Seite angefasst ist — von hier aus misst jede Einheit genau ihre eigene Arbeit.
Warum der Bump vor der Korpusarbeit kam statt danach, wie ursprünglich geplant: CIs Version-Gate verweigert eine Stack-Änderung ohne Versionsbewegung, und u0 hat tools/, types/, instructions/ und vier Contracts angefasst. Die Einheiten u1–u4 fassen nur kb/ an, brauchen also keinen weiteren Bump — und werden von CI ohnehin über paths-ignore ausgeschlossen.
Stand der Akzeptanzkriterien
Kein Überschriftentext in tools/ — sections.py und PRE_CONVENTIONS_NAMES existieren nicht mehr
related: trägt Label als Enum-Wert; lint prüft gegen outbound: der Quellcollection
xref add schreibt genau eine Kante; die eingehende Sicht ist gerendert (links show), nicht gespeichert
Marker-Paar-Invariante in migrate verify
instructions/link-taxonomy.md enthält alle 26 Commonplace-Label plus die neun operativen
Ein Label hinzufügen braucht keine Codeänderung
migrate status trennt required von offered und blockiert nie auf offered
migrate status meldet, welche instanzeigenen Dateien von den Stamp-Hashes abweichen
Die vier Page-Type-Specs sind nicht im Distributions-Dateiplan; nur ihre .template
docs verify prüft STACK_REQUIRED_TYPES
Version-Bump nach version-parts.md, Grenzübertritt gesetzt, Migrationsdokument vorhanden
Offen bleibt allein der Bestand: 480 ungelabelte Kanten über u1–u4, danach u5 (Beförderung der beiden Lint-Findings zu Hard Errors, migrate done 4.0.0, Workshop schließen).
## u0 publiziert als `4.0.0` — `177c7e9`
56 Dateien, +2.695/-753. `VERSION` auf `4.0.0`, Migrationsdokument `instructions/migrations/4.0.0-link-taxonomy.md` (`assisted`, `required`), Baum sauber.
**Dieser Commit ist die Baseline für u1–u4.** `migrate verify --from 177c7e9` vergleicht gegen einen Stand, in dem die Marker-Maschinerie existiert und noch keine Seite angefasst ist — von hier aus misst jede Einheit genau ihre eigene Arbeit.
Warum der Bump *vor* der Korpusarbeit kam statt danach, wie ursprünglich geplant: CIs Version-Gate verweigert eine Stack-Änderung ohne Versionsbewegung, und u0 hat `tools/`, `types/`, `instructions/` und vier Contracts angefasst. Die Einheiten u1–u4 fassen nur `kb/` an, brauchen also keinen weiteren Bump — und werden von CI ohnehin über `paths-ignore` ausgeschlossen.
### Stand der Akzeptanzkriterien
- [x] Kein Überschriftentext in `tools/` — `sections.py` und `PRE_CONVENTIONS_NAMES` existieren nicht mehr
- [x] `related:` trägt Label als Enum-Wert; `lint` prüft gegen `outbound:` der Quellcollection
- [x] `xref add` schreibt genau eine Kante; die eingehende Sicht ist gerendert (`links show`), nicht gespeichert
- [x] Marker-Paar-Invariante in `migrate verify`
- [x] `instructions/link-taxonomy.md` enthält alle 26 Commonplace-Label plus die neun operativen
- [x] Ein Label hinzufügen braucht keine Codeänderung
- [x] `migrate status` trennt `required` von `offered` und blockiert nie auf `offered`
- [x] `migrate status` meldet, welche instanzeigenen Dateien von den Stamp-Hashes abweichen
- [x] Die vier Page-Type-Specs sind nicht im Distributions-Dateiplan; nur ihre `.template`
- [x] `docs verify` prüft `STACK_REQUIRED_TYPES`
- [x] Version-Bump nach `version-parts.md`, Grenzübertritt gesetzt, Migrationsdokument vorhanden
Offen bleibt allein der Bestand: **480 ungelabelte Kanten** über u1–u4, danach u5 (Beförderung der beiden Lint-Findings zu Hard Errors, `migrate done 4.0.0`, Workshop schließen).
Korpus-Migration abgeschlossen (Abschnitte 2 und 3)
Die Korpus-Hälfte dieses Issues ist durch. kb_version steht auf 4.0.0, lint --fail-on-error und docs verify sind grün, der Workshop work/link-taxonomy-migration/ ist geschlossen und gelöscht.
Umfang: 152 Seiten, ~530 gelabelte Kanten, in fünf Einheiten über mehrere Sitzungen:
kb/entities/ projects, people + kb/comparisons/ — 16 Seiten, ~45 Kanten
9e2f9bf
u3
kb/concepts/ — 80 Seiten, ~250 Kanten
a35c94e
u4
kb/sources/ — 0 Seiten (siehe unten)
—
u5
37 Restkanten, migrate done 4.0.0, Workshop-Close
3f99d67
Endstand lint: 0 unlabelled_edges, 0 unauthorised_labels, 0 malformed_edges, 0 unbalanced_markers, 0 broken_links, 0 dangling_frontmatter_refs, 0 schema_validation_errors. Damit ist Schritt 6 des Migrationsdokuments erfüllt — die beiden Findings können jetzt zu harten Fehlern promoted werden (nicht Teil dieses Laufs, siehe unten).
Drei Befunde, die im Entwurf nicht standen
1. comparison und source haben gar kein related:-Feld. Beide Type-Specs deklarieren nur page_ref_fields: [entities] bzw. [entities, concepts]; xref add verweigert dort. Die outbound:-Blöcke in kb/comparisons/COLLECTION.md (compares-with, contrasts, see-also) und kb/sources/COLLECTION.md (is-evidence-for, defined-in, see-also) autorisieren damit Label, die auf diesen Seiten strukturell nicht geschrieben werden können — die Autorisierung läuft ins Leere. Für kb/sources/ war das folgenlos (entities:/concepts: waren beim Ingest über xref link-source korrekt gefüllt, migrate verify --path kb/sources meldet 0 Befunde gegen die Baseline); bei kb/comparisons/ wurde das Label per Hand in die Prosa geschrieben. Entweder sollten die beiden Type-Specs ein related: bekommen, oder die outbound:-Blöcke dort sollten weg — im jetzigen Zustand behauptet der Contract eine Fähigkeit, die es nicht gibt.
2. Dem Katalog fehlt ein Register für Urheberschaft. Kein Label trägt „Person hat X erschaffen" oder „X baut auf der Arbeit von Y auf". owns deckt fortlaufende Verantwortlichkeit ab (so benutzt für Vannevar Bush owns Memex, Rohit Gupta owns Agent Memory, E3DC GmbH owns E3DC), aber der einmalige historische Akt — Karpathy formuliert das LLM Wiki Pattern, Gupta erweitert Karpathys Arbeit — hat in Realization und Lineage nichts, was für eine Person autorisiert wäre: die Register sind concept↔concept oder concept↔entity, nie entity↔entity oder entity↔concept für Autorschaft. Alle diese Kanten stehen jetzt auf see-also, nach Prüfung jedes einzelnen Registers. Kandidat für eine Katalog-Zeile (originates? authored-by?), falls das wiederkehrt.
3. part-of/composition verhalten sich in der Praxis wie ein Inversenpaar, sind aber keins. Der Katalog nennt ausdrücklich nur depends-on/required-by und runs-on/hosts als Paare mit eigener Gegenaussage. Bei Eltern-Kind-Strukturen (Consolidation Tiers und seine vier Ebenen, Implementation Spectrum und seine Level, Multi-Agent Collaboration und seine drei Komponenten) entsteht dadurch regelmäßig die Frage, ob die Gegenseite part-of schreiben darf, wenn das Elternteil schon composition hat. Regel im Lauf: nein — die zweite Seite bekommt see-also. Das erzeugt sichtbar mehr see-also, als der Entwurf wollte; ob das die richtige Auflösung ist, wäre eine bewusste Katalog-Entscheidung wert.
Zwei Fehler im Lauf, beide selbst gefunden
14 Seiten blieben unlabelled, weil ich Kanten mit der Begründung „steht schon auf der Gegenseite" ganz übersprang, statt sie zu belegen — die Gegenseite trug die Kante, das Frontmatter dieser Seite blieb aber ein nackter Titel. Gefunden durch lint --fulls unlabelled_edges-Bericht nach dem ersten u3-Durchlauf.
Ein echter Inhaltsverlust: beim Aufräumen der Prosa auf Personalization Plane.md entfernte ich eine inhaltlich eigenständige, mit Fußnote belegte Zeile mit, weil ich sie für ein bloßes Wikilink-Bullet hielt. migrate verify fing das über cite-refs 2->1 — der Marker-Paar- und Zähl-Invariant aus Abschnitt 3 dieses Issues hat also im ersten Ernstfall genau das getan, wofür er gebaut wurde.
Dazu eine Selbstkorrektur: die Asymmetrie „xref add schreibt nur A, xref remove räumt beide Seiten" habe ich in u3 als Erkenntnis notiert — sie steht wörtlich in tools/CONTRACT.md Zeilen 43–44. Sie war nachzulesen, nicht zu entdecken; in u1 kostete das mehrere unnötig defensive Entscheidungen (die 37 Restkanten in u5 sind genau diese Vorsicht).
Was an diesem Issue offen bleibt
Die Abschnitte 1 und 4 sind unberührt: obligation: offered, der Stamp-Hash-Abgleich in migrate status, die Aufteilung von types/ entlang root: und STACK_REQUIRED_TYPES. Ebenso offen die Promotion von unlabelled_edges/unauthorised_labels zu harten Fehlern — der Korpus erfüllt sie jetzt, der Schritt selbst ist eine eigene Stack-Änderung.
## Korpus-Migration abgeschlossen (Abschnitte 2 und 3)
Die Korpus-Hälfte dieses Issues ist durch. `kb_version` steht auf `4.0.0`, `lint --fail-on-error` und `docs verify` sind grün, der Workshop `work/link-taxonomy-migration/` ist geschlossen und gelöscht.
**Umfang:** 152 Seiten, ~530 gelabelte Kanten, in fünf Einheiten über mehrere Sitzungen:
| Einheit | Umfang | Commit |
|---|---|---|
| u1 | `kb/entities/` systems, tools, technologies — 57 Seiten, 199 Kanten | `b137359` |
| u2 | `kb/entities/` projects, people + `kb/comparisons/` — 16 Seiten, ~45 Kanten | `9e2f9bf` |
| u3 | `kb/concepts/` — 80 Seiten, ~250 Kanten | `a35c94e` |
| u4 | `kb/sources/` — 0 Seiten (siehe unten) | — |
| u5 | 37 Restkanten, `migrate done 4.0.0`, Workshop-Close | `3f99d67` |
**Endstand `lint`:** 0 `unlabelled_edges`, 0 `unauthorised_labels`, 0 `malformed_edges`, 0 `unbalanced_markers`, 0 `broken_links`, 0 `dangling_frontmatter_refs`, 0 `schema_validation_errors`. Damit ist Schritt 6 des Migrationsdokuments erfüllt — die beiden Findings können jetzt zu harten Fehlern promoted werden (nicht Teil dieses Laufs, siehe unten).
### Drei Befunde, die im Entwurf nicht standen
**1. `comparison` und `source` haben gar kein `related:`-Feld.** Beide Type-Specs deklarieren nur `page_ref_fields: [entities]` bzw. `[entities, concepts]`; `xref add` verweigert dort. Die `outbound:`-Blöcke in `kb/comparisons/COLLECTION.md` (`compares-with`, `contrasts`, `see-also`) und `kb/sources/COLLECTION.md` (`is-evidence-for`, `defined-in`, `see-also`) autorisieren damit Label, die auf diesen Seiten strukturell nicht geschrieben werden können — die Autorisierung läuft ins Leere. Für `kb/sources/` war das folgenlos (`entities:`/`concepts:` waren beim Ingest über `xref link-source` korrekt gefüllt, `migrate verify --path kb/sources` meldet 0 Befunde gegen die Baseline); bei `kb/comparisons/` wurde das Label per Hand in die Prosa geschrieben. **Entweder** sollten die beiden Type-Specs ein `related:` bekommen, **oder** die `outbound:`-Blöcke dort sollten weg — im jetzigen Zustand behauptet der Contract eine Fähigkeit, die es nicht gibt.
**2. Dem Katalog fehlt ein Register für Urheberschaft.** Kein Label trägt „Person hat X erschaffen" oder „X baut auf der Arbeit von Y auf". `owns` deckt fortlaufende Verantwortlichkeit ab (so benutzt für `Vannevar Bush owns Memex`, `Rohit Gupta owns Agent Memory`, `E3DC GmbH owns E3DC`), aber der einmalige historische Akt — Karpathy formuliert das LLM Wiki Pattern, Gupta erweitert Karpathys Arbeit — hat in Realization und Lineage nichts, was für eine Person autorisiert wäre: die Register sind concept↔concept oder concept↔entity, nie entity↔entity oder entity↔concept für Autorschaft. Alle diese Kanten stehen jetzt auf `see-also`, nach Prüfung jedes einzelnen Registers. Kandidat für eine Katalog-Zeile (`originates`? `authored-by`?), falls das wiederkehrt.
**3. `part-of`/`composition` verhalten sich in der Praxis wie ein Inversenpaar, sind aber keins.** Der Katalog nennt ausdrücklich nur `depends-on`/`required-by` und `runs-on`/`hosts` als Paare mit eigener Gegenaussage. Bei Eltern-Kind-Strukturen (Consolidation Tiers und seine vier Ebenen, Implementation Spectrum und seine Level, Multi-Agent Collaboration und seine drei Komponenten) entsteht dadurch regelmäßig die Frage, ob die Gegenseite `part-of` schreiben darf, wenn das Elternteil schon `composition` hat. Regel im Lauf: nein — die zweite Seite bekommt `see-also`. Das erzeugt sichtbar mehr `see-also`, als der Entwurf wollte; ob das die richtige Auflösung ist, wäre eine bewusste Katalog-Entscheidung wert.
### Zwei Fehler im Lauf, beide selbst gefunden
- **14 Seiten blieben unlabelled**, weil ich Kanten mit der Begründung „steht schon auf der Gegenseite" ganz übersprang, statt sie zu belegen — die Gegenseite trug die Kante, das Frontmatter dieser Seite blieb aber ein nackter Titel. Gefunden durch `lint --full`s `unlabelled_edges`-Bericht nach dem ersten u3-Durchlauf.
- **Ein echter Inhaltsverlust:** beim Aufräumen der Prosa auf `Personalization Plane.md` entfernte ich eine inhaltlich eigenständige, mit Fußnote belegte Zeile mit, weil ich sie für ein bloßes Wikilink-Bullet hielt. `migrate verify` fing das über `cite-refs 2->1` — der Marker-Paar- und Zähl-Invariant aus Abschnitt 3 dieses Issues hat also im ersten Ernstfall genau das getan, wofür er gebaut wurde.
Dazu eine Selbstkorrektur: die Asymmetrie „`xref add` schreibt nur A, `xref remove` räumt beide Seiten" habe ich in u3 als Erkenntnis notiert — sie steht wörtlich in `tools/CONTRACT.md` Zeilen 43–44. Sie war nachzulesen, nicht zu entdecken; in u1 kostete das mehrere unnötig defensive Entscheidungen (die 37 Restkanten in u5 sind genau diese Vorsicht).
### Was an diesem Issue offen bleibt
Die Abschnitte 1 und 4 sind unberührt: `obligation: offered`, der Stamp-Hash-Abgleich in `migrate status`, die Aufteilung von `types/` entlang `root:` und `STACK_REQUIRED_TYPES`. Ebenso offen die Promotion von `unlabelled_edges`/`unauthorised_labels` zu harten Fehlern — der Korpus erfüllt sie jetzt, der Schritt selbst ist eine eigene Stack-Änderung.
Changelog: Body auf den Abschlussstand gezogen. Neu: Status-Zeile, Abschnitt 5. Abschluss (4.1.0), alle Kriterien abgehakt, das nachgereichte zwölfte Kriterium aus dem Kommentar vom 2026-09-02 aufgenommen, Abschnitt Ausgelagert mit #43. Korrigiert: die Reihenfolge steht jetzt benannt im Body statt nur im ersten Kommentar; die Inversenpaare sind drei statt zwei; die falsche 1:1-Migrationsabbildung ist durch die Messung ersetzt; die vier Umsetzungskorrekturen aus dem u0-Kommentar stehen im Body statt nur in der Historie. Entfernt: die widerlegte Behauptung, sections: falle weg und index rebuild rendere die Eingangssicht.
Abschluss
Die letzten drei offenen Punkte sind als 4.1.0 publiziert, Commit cfe925a. Damit ist dieses Issue vollständig.
Was in diesem Lauf umgesetzt wurde
Die Beförderung der beiden Lint-Findings — aber gebunden statt umgelegt. Der Entwurf sagte "werden hart, nachdem die Migration aufgezeichnet ist", und das wäre als Konstante im Code genau der Weg von legacy_citation_markers gewesen: in einer späteren Version umgelegt, und jede Instanz, die die Migration noch schuldete, lebte danach mit rotem Lint. Für 4.0.0 wäre das schlimmer gewesen, weil migrate-corpus.md einen Lauf in Einheiten mit je eigenem Publish vorschreibt — ein lint --fail-on-error, das dabei fehlschlägt, verweigert genau den Korpus, dessen Fortschritt es misst.
Also hard_error_keys() gegen kb_version: unterhalb 4.0.0 beratend, ab 4.0.0 hart, ohne Stamp-Datei bleibt alles hart (eine frische Instanz startet in der aktuellen Form, statt in sie hineinzumigrieren). Das ist der Punkt, an dem Abschnitt 1 dieses Issues sich auszahlt — das Ledger kann die Frage inzwischen beantworten, also muss keine Datumskonstante sie raten. HARD_ERROR_KEYS bleibt die vollständige Menge, hard_error_keys() liefert die jeweils geltende.
Befund 1 — und er war folgenreicher als im Abschlusskommentar notiert. Dass kb/comparisons/ und kb/sources/ Label autorisierten, die ihre Type-Specs strukturell nicht schreiben können, blieb nicht folgenlos: die einzige Comparison-Seite des Korpus trug
als handgeschriebene Prosa — ohne Marker-Region, ohne Frontmatter-Deckung, für lint unsichtbar, weil comparison kein related: führte. Also ein Identifier zurück im Fließtext, geschrieben in u2, wenige Stunden nach dem Commit, der genau das beendet hatte. Eine leere Autorisierung liest sich als Lizenz; wer sie liest und keinen Träger findet, schreibt das Label in die Prosa.
Aufgelöst nach dem, was die beiden Contracts jeweils selbst sagen — asymmetrisch, weil die beiden Fälle es sind:
Auflösung
Warum
comparison
bekommt related: in page_ref_fields und Schema
Die compares-with-Kante gegen jedes Subjekt ist die eine Aussage, für die die Seite existiert; entities: ist die untypisierte Liste daneben
kb/sources/
verliert den outbound:-Block ersatzlos
Der Contract sagt ausdrücklich, seine Verknüpfungen seien der mechanische Provenance-Pfad (raw_files:, entities:, concepts:, [^cite-id]) und keine Autorenkanten
Dazu die Prüfung, die die beiden Hälften künftig zusammenhält: docs verify meldet einen outbound:-Block auf einer Collection, in die kein Typ mit related: schreibt, und nennt beide Richtungen der Reparatur. Die Comparison-Seite trägt ihre zwei Kanten jetzt als deklarierte Kanten in einer wikitool:links-Region.
Befund 3 — composition/part-of ist das dritte Inversenpaar. Die u3-Regel ("die Gegenseite bekommt see-also, weil part-of ein Spiegel wäre") ist zurückgenommen: der Satz des Elternteils zählt seine Teile auf, der des Kindes benennt das Ganze, zu dem es gehört. Zwei Sätze, nicht einer zweimal. 16 see-also-Kanten sind auf part-of relabelt — Consolidation Tiers, Content Quality Control, Hybrid Search, Knowledge Graph, LLM Wiki Pattern, Memory Lifecycle, Multi-Agent Collaboration, Wine GE, farzaa gist und ihre Kinder.
Präzisiert dabei, weil es sonst gegen "Richtung wird verfasst, nicht gespiegelt" liefe: ein Inversenpaar macht die Gegenkante nicht zur Pflicht. Es legt fest, welches Label sie trägt, wenn jemand sie schreibt. Zehn composition-Kanten haben weiterhin keine Gegenkante, und das ist kein Befund — vor allem die sechs Ebenen des Implementation Spectrum, deren Zugehörigkeit zu einem Spektrum eine schwächere Aussage ist als die Teil-Ganzes-Beziehung, die part-of behauptet.
Was bewusst nicht umgesetzt wurde
Befund 2, das fehlende Register für Urheberschaft — ausgelagert nach #43, kind/decision / prio/waiting. Es erweitert den Katalog, statt einen Widerspruch zu beheben, und der Zuschnitt (eigenes Register oder Lineage? ein Label oder zwei? Inversenpaar?) lässt sich an drei Fällen nur raten. Der Korpus ist in der Zwischenzeit konsistent: alle betroffenen Kanten stehen auf see-also, keine trägt ein falsches Label.
Der Versionsteil war eine Abwägung, keine Formsache. Der neue docs verify-Check hätte eine bestehende 4.0.x-Instanz beim bloßen Kopieren der Maschinerie fehlschlagen lassen, weil ihre kb/sources/COLLECTION.md den outbound:-Block noch trägt — nach version-parts.md Schritt 1 ein Grenzübertritt. Erwogen und verworfen wurden eine 5.0.0 mit Pflichtmigration (zwei Zeilen löschen, dafür zahlt jede Instanz einen Major) und die Abstufung des Checks auf einen doctor-WARN (Drop-in, aber die Prüfung wäre schwächer als der Befund, den sie gefunden hat). Stattdessen ist die Ursache mitbeseitigt: die Collection-Contracts dieser Instanz sind angepasst, dist export leitet die COLLECTION.md.template daraus ab, also liefert jede neue Distribution die korrigierte Form aus. Für eine bereits bestehende 4.0.x-Instanz bleibt eine Handbewegung, und sie steht namentlich in CHANGES.md statt versteckt zu sein.
Nebenbefund, eigenes Issue
#44 — die kb_dir-Fixture der Testsuite repointet config.ROOT nicht. Aufgefallen beim Bau der Gate-Tests: ein Test überschrieb das echte .wikitool-kb.json des Repos (zurückgeholt, Tests auf eine lokale rooted_kb-Fixture umgestellt). Die stillere Hälfte wiegt schwerer — lint_core._collection_of() löst gegen config.KB_DIR statt gegen den übergebenen Baum auf, weshalb unauthorised_labels in der Suite bis 4.1.0 faktisch nicht getestet war.
Verifiziert
863 Tests, docs verify, instructions verify, doctor, lint --fail-on-error und dist export grün. migrate verify --from HEAD: 181 Seiten verglichen, 0 hinzugefügt, 0 entfernt, 18 Befunde — sämtlich Label-Wechsel im Frontmatter, keine Änderung an Wikilink- oder Zitatzahlen, keine Marker-Paar-Differenz.
**Changelog:** Body auf den Abschlussstand gezogen. Neu: Status-Zeile, Abschnitt *5. Abschluss (4.1.0)*, alle Kriterien abgehakt, das nachgereichte zwölfte Kriterium aus dem Kommentar vom 2026-09-02 aufgenommen, Abschnitt *Ausgelagert* mit #43. Korrigiert: die Reihenfolge steht jetzt benannt im Body statt nur im ersten Kommentar; die Inversenpaare sind drei statt zwei; die falsche 1:1-Migrationsabbildung ist durch die Messung ersetzt; die vier Umsetzungskorrekturen aus dem u0-Kommentar stehen im Body statt nur in der Historie. Entfernt: die widerlegte Behauptung, `sections:` falle weg und `index rebuild` rendere die Eingangssicht.
---
## Abschluss
Die letzten drei offenen Punkte sind als **`4.1.0`** publiziert, Commit `cfe925a`. Damit ist dieses Issue vollständig.
### Was in diesem Lauf umgesetzt wurde
**Die Beförderung der beiden Lint-Findings — aber gebunden statt umgelegt.** Der Entwurf sagte "werden hart, nachdem die Migration aufgezeichnet ist", und das wäre als Konstante im Code genau der Weg von `legacy_citation_markers` gewesen: in einer späteren Version umgelegt, und jede Instanz, die die Migration noch schuldete, lebte danach mit rotem Lint. Für 4.0.0 wäre das schlimmer gewesen, weil `migrate-corpus.md` einen Lauf in Einheiten mit je eigenem Publish vorschreibt — ein `lint --fail-on-error`, das dabei fehlschlägt, verweigert genau den Korpus, dessen Fortschritt es misst.
Also `hard_error_keys()` gegen `kb_version`: unterhalb 4.0.0 beratend, ab 4.0.0 hart, ohne Stamp-Datei bleibt alles hart (eine frische Instanz startet in der aktuellen Form, statt in sie hineinzumigrieren). Das ist der Punkt, an dem Abschnitt 1 dieses Issues sich auszahlt — das Ledger kann die Frage inzwischen beantworten, also muss keine Datumskonstante sie raten. `HARD_ERROR_KEYS` bleibt die vollständige Menge, `hard_error_keys()` liefert die jeweils geltende.
**Befund 1 — und er war folgenreicher als im Abschlusskommentar notiert.** Dass `kb/comparisons/` und `kb/sources/` Label autorisierten, die ihre Type-Specs strukturell nicht schreiben können, blieb nicht folgenlos: die einzige Comparison-Seite des Korpus trug
```
## Beziehungen
- **compares-with:** [[amd-pstate]]
- **compares-with:** [[acpi-cpufreq]]
```
als **handgeschriebene Prosa** — ohne Marker-Region, ohne Frontmatter-Deckung, für `lint` unsichtbar, weil `comparison` kein `related:` führte. Also ein Identifier zurück im Fließtext, geschrieben in u2, wenige Stunden nach dem Commit, der genau das beendet hatte. Eine leere Autorisierung liest sich als Lizenz; wer sie liest und keinen Träger findet, schreibt das Label in die Prosa.
Aufgelöst nach dem, was die beiden Contracts jeweils selbst sagen — asymmetrisch, weil die beiden Fälle es sind:
| | Auflösung | Warum |
|---|---|---|
| `comparison` | bekommt `related:` in `page_ref_fields` und Schema | Die `compares-with`-Kante gegen jedes Subjekt ist die eine Aussage, für die die Seite existiert; `entities:` ist die untypisierte Liste daneben |
| `kb/sources/` | verliert den `outbound:`-Block ersatzlos | Der Contract sagt ausdrücklich, seine Verknüpfungen seien der mechanische Provenance-Pfad (`raw_files:`, `entities:`, `concepts:`, `[^cite-id]`) und keine Autorenkanten |
Dazu die Prüfung, die die beiden Hälften künftig zusammenhält: `docs verify` meldet einen `outbound:`-Block auf einer Collection, in die kein Typ mit `related:` schreibt, und nennt beide Richtungen der Reparatur. Die Comparison-Seite trägt ihre zwei Kanten jetzt als deklarierte Kanten in einer `wikitool:links`-Region.
**Befund 3 — `composition`/`part-of` ist das dritte Inversenpaar.** Die u3-Regel ("die Gegenseite bekommt `see-also`, weil `part-of` ein Spiegel wäre") ist zurückgenommen: der Satz des Elternteils zählt seine Teile auf, der des Kindes benennt das Ganze, zu dem es gehört. Zwei Sätze, nicht einer zweimal. 16 `see-also`-Kanten sind auf `part-of` relabelt — Consolidation Tiers, Content Quality Control, Hybrid Search, Knowledge Graph, LLM Wiki Pattern, Memory Lifecycle, Multi-Agent Collaboration, Wine GE, farzaa gist und ihre Kinder.
Präzisiert dabei, weil es sonst gegen "Richtung wird verfasst, nicht gespiegelt" liefe: ein Inversenpaar macht die Gegenkante **nicht** zur Pflicht. Es legt fest, welches Label sie trägt, wenn jemand sie schreibt. Zehn `composition`-Kanten haben weiterhin keine Gegenkante, und das ist kein Befund — vor allem die sechs Ebenen des Implementation Spectrum, deren Zugehörigkeit zu einem Spektrum eine schwächere Aussage ist als die Teil-Ganzes-Beziehung, die `part-of` behauptet.
### Was bewusst nicht umgesetzt wurde
**Befund 2**, das fehlende Register für Urheberschaft — ausgelagert nach **#43**, `kind/decision` / `prio/waiting`. Es erweitert den Katalog, statt einen Widerspruch zu beheben, und der Zuschnitt (eigenes Register oder Lineage? ein Label oder zwei? Inversenpaar?) lässt sich an drei Fällen nur raten. Der Korpus ist in der Zwischenzeit konsistent: alle betroffenen Kanten stehen auf `see-also`, keine trägt ein falsches Label.
**Der Versionsteil war eine Abwägung, keine Formsache.** Der neue `docs verify`-Check hätte eine bestehende 4.0.x-Instanz beim bloßen Kopieren der Maschinerie fehlschlagen lassen, weil ihre `kb/sources/COLLECTION.md` den `outbound:`-Block noch trägt — nach `version-parts.md` Schritt 1 ein Grenzübertritt. Erwogen und verworfen wurden eine `5.0.0` mit Pflichtmigration (zwei Zeilen löschen, dafür zahlt jede Instanz einen Major) und die Abstufung des Checks auf einen `doctor`-WARN (Drop-in, aber die Prüfung wäre schwächer als der Befund, den sie gefunden hat). Stattdessen ist die **Ursache mitbeseitigt**: die Collection-Contracts dieser Instanz sind angepasst, `dist export` leitet die `COLLECTION.md.template` daraus ab, also liefert jede neue Distribution die korrigierte Form aus. Für eine bereits bestehende 4.0.x-Instanz bleibt eine Handbewegung, und sie steht namentlich in `CHANGES.md` statt versteckt zu sein.
### Nebenbefund, eigenes Issue
**#44** — die `kb_dir`-Fixture der Testsuite repointet `config.ROOT` nicht. Aufgefallen beim Bau der Gate-Tests: ein Test überschrieb das echte `.wikitool-kb.json` des Repos (zurückgeholt, Tests auf eine lokale `rooted_kb`-Fixture umgestellt). Die stillere Hälfte wiegt schwerer — `lint_core._collection_of()` löst gegen `config.KB_DIR` statt gegen den übergebenen Baum auf, weshalb `unauthorised_labels` in der Suite bis 4.1.0 faktisch nicht getestet war.
### Verifiziert
863 Tests, `docs verify`, `instructions verify`, `doctor`, `lint --fail-on-error` und `dist export` grün. `migrate verify --from HEAD`: 181 Seiten verglichen, 0 hinzugefügt, 0 entfernt, 18 Befunde — sämtlich Label-Wechsel im Frontmatter, keine Änderung an Wikilink- oder Zitatzahlen, keine Marker-Paar-Differenz.
Anschluss laut #39: **#28**, danach **#30**.
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.
Der Befund
#39 hat die Eigentumsfrage richtig beantwortet und den Mechanismus stehen lassen, der sie überhaupt schwer gemacht hat. Der Stack benutzte an drei Stellen Prosa als Identifier:
## Beziehungen(Überschriftentext)hängt ab von(Label im Body-Bullet)related:{section.relationships}undPRE_CONVENTIONS_NAMESaus 3.0.0 waren die Kompensation dafür, nicht der Entwurf. Sie sind entfallen.Der Preis des Matchens auf Prosa stand schon im Repo: der Docstring von
provenance.split_cite_blockerzählt, wie acht Seiten Inhalt verloren, weil eine toolgeführte Region per Heuristik statt per Begrenzer gefunden wurde.Entscheidungen
Getroffen in der Sitzung vom 2026-09-02, im Anschluss an #39.
Kantenmodell: direktional
Die erzwungene Bidirektionalität (
kb/CONTRACT.md: "Every page links to what it mentions, in both directions",xref addschrieb beide Seiten) ist entfallen.Sie ist mit der per-Collection-Autorisierung logisch unverträglich: die läuft pro Quell→Ziel-Paarung und steht in der
COLLECTION.mdder Quelle. Schreibtxref adddie Gegenkante mit, entsteht eine Kante, deren Quellcollection eine andere ist — deren Regeln der Autor weder liest noch überblickt. Entweder das Werkzeug schreibt unautorisierte Kanten, oder die Regel "die Quellcollection entscheidet" löst sich auf.Der Navigationseinwand ("ich lande auf Hermes und will sehen, was davon abhängt") ist durch die Umstellung besser gelöst als vorher:
related:trägt maschinenlesbare Label, also ist die eingehende Liste ableitbar — gerendert vonwikitool links show, vollständig und ohne Pflege. Die erzwungene Gegenkante garantierte das nie, sie garantierte nur, dass jemand daran gedacht hat.Nicht in diesem Paket: der Articulation-Test und der
connect-Report-Workflow aus Commonplace. Die ändern, wie Ingest arbeitet, nicht wie Links gespeichert werden.Label sind Enum-Werte, nicht lokalisiert
- **depends_on:** [[Hermes]]steht so in einer deutschen Seite. Keine Anzeigetabelle, kein Vollständigkeitscheck, keine dreizehn Zeilen Instanzdaten. Der Enum-Wert ist der Anzeigetext.Umkehrbar: eine Anzeigetabelle bliebe jederzeit rein additiv nachrüstbar, weil der Enum-Wert der Identifier bleibt.
depends-on/required-bydepended-byist nicht grammatisch — "depend" ist intransitiv und verlangt "on"; das Passiv wäredepended-on-by. Die etablierte Umkehrung der Domäne istrequired-by(rpm; apt nennt es "Reverse Depends"). Beide erfüllen Commonplaces ADR-058-Invariantesource <label> target.Das bisherige
benötigtist entfallen — es war immer ein Synonym vonhängt ab von.Die Taxonomie
Sechs Register. Die ersten beiden sind neu und folgen dem Profil dieser Instanz (Infrastruktur, Architektur); die vier danach sind Commonplaces Katalog vollständig übernommen (alle 26).
depends-on·required-by·runs-on·hosts·uses·produces·consumes·maintains·ownsimplements·operationalized-from·mechanism·procedure·applies-when·operates-on·invokesextends·grounds·rests-on·enables·precondition·exemplifies·abstracted-from·contrasts·compares-with·contradicts·composition·part-ofsupersedes·derived-from·adapted-from·defined-inevidenced-by·is-evidence-forsee-also(letzte Instanz)35 Labels. Inverse nur, wo die Gegenrichtung eine eigene Primäraussage ist. Der Entwurf nannte zwei Paare; seit 4.1.0 sind es drei:
depends-on/required-by,runs-on/hostsundcomposition/part-of. Alles andere hat keins — die eingehende Sicht wird gerendert. Ein Inversenpaar macht die Gegenkante nicht zur Pflicht; es legt fest, welches Label sie trägt, wenn jemand sie schreibt.Wo was liegt:
instructions/link-taxonomy.md— der Katalog. Stack, Palette, bindet nichts.kb/<name>/COLLECTION.mdoutbound:— die autorisierte Teilmenge pro Quell→Ziel-Paarung. Instanz, bindet. (Commonplace ADR-019)lintprüftrelated:gegen diese Autorisierung.Ein Label hinzufügen ist eine Zeile Daten, keine Codeänderung.
Zur Migrationsabbildung: der Entwurf behauptete eine 1:1-Abbildung von dreizehn Labeln. Gemessen am Korpus war das falsch — 152 distinkte Label in 337 Bullets, davon 102 mit genau einem Vorkommen, dazu 555 nackte
## Siehe auch-Bullets. Der Lauf war deshalbassistedund redaktionell, ohne mechanischen Boden; die Messung steht im Kommentar vom 2026-09-02.Umsetzung
Reihenfolge, benannt statt nummeriert (Korrektur im ersten Kommentar): Abschnitt 1 → Abschnitt 4 → Abschnitte 2+3.
1. Angebotene Migrationen (
obligation:) — [x] erledigtZwei Achsen statt einer.
migration_kind:beschreibt wie die Arbeit läuft,obligation:ob sie laufen muss:required(Default)offeredchain()zählt nurrequired;offers()gibt den Rest. Korrektur gegen den Entwurf: Offers sind gegen das Applied-Ledger gebunden, nicht gegenkb_version— eine angenommene Offer bewegtkb_versiongerade nicht, also kann die Version nichts darüber aussagen, und ein unbeteiligter Pflichtschritt hätte jede Offer stillschweigend verschwinden lassen.migrate done <offer>schreibt einen Ledger-Eintrag ohne Versionsbewegung und ohne Reihenfolgeregel.kb_state.divergent_files()liest die sha256 pro Datei aus.wikitool-release.jsonund beantwortet "editiert oder nur empfangen".Nonestatt[]ohne Stamp — ein Entwicklungsbaum hat keinen, und "nichts abgewichen" wäre dort eine erfundene Antwort.4.
types/teilt sich entlangroot:— [x] erledigtentity,concept,source,comparison.template, adoptiert per Renameinstruction,lint-report,type-specdist exportschlüsselt die vierroot: kb-Type-Specs samt ihrer.schema.yamlals.templateum; abgeleitet austypes/, nicht gelistet. Damit ist die deutsche Prosa in den vier Page-Type-Specs korrekt statt Migrationsschuld.Der Stack nennt sein Minimum:
STACK_REQUIRED_TYPES = ("source",)— muss existieren, Schema mussraw_filesverlangen.STACK_REQUIRED_COLLECTIONSals separate Liste ist weg;stack_required_collections()leitet sie aus dembase_dirdessource-Typs ab.provenance._SOURCE_TITLE_PREFIXliesttitle_prefix:aus dem Type-Spec.Die Vorbedingung hält:
source_pages_by_raw_file,legacy_source_pagesundduplicate_raw_file_ownersfragen allepage.kind, also dasname:des Type-Specs — nicht Verzeichnis oder Titelpräfix. Die Ankermenge blieb so klein wie beschrieben.2+3. Label werden Enum, toolgeführte Blöcke werden generierte Regionen — [x] erledigt
Eine Korpusmigration, weil beide jede Seite anfassen.
Gelöscht, ersatzlos:
tools/chemenu/sections.py,heading_re, der Alias-Mechanismus,PRE_CONVENTIONS_NAMES,cite_block_heading,provenance.__getattr__, die{section.*}-Template-Variablen undxrefs Abschnittssuche. Kein Überschriftentext liegt mehr in Python.Neu:
blocks.py(Marker-Regionen<!-- wikitool:links -->/<!-- wikitool:footnotes -->),links.py,instructions/link-taxonomy.md,outbound:in denCOLLECTION.md,links showals abgeleitete Eingangssicht.Vier Korrekturen, die die Umsetzung erzwungen hat:
sections:fällt nicht weg. Die Region rendert ihre Überschrift mit, braucht also einen Text. Was wegfällt, ist das Matching — der Wert ist Anzeige, nicht Identifier. Von drei Slots auf zwei:linksundfootnotes.see-also-Region. Sie war die Spiegelhälfte des bidirektionalenxref add; unter direktionalen Kanten istsee-alsoein Label innerhalb der Links-Region.index rebuildrendert die Eingangssicht nicht. Eine Backlink-Liste pro Seite in einen Shard zu schreiben, der ohnehin eine Seitentabelle ist, verdoppelt ihn. Stattdessenlinks show --page, budgetbefreit wiesearch. Dazu: das gerenderte Bullet ist ein Wikilink, also zeigt Obsidians Backlink-Panel die Eingangssicht ohnehin.xrefgelaufen".5. Abschluss (4.1.0) — [x] erledigt
unlabelled_edgesundunauthorised_labelssind harte Fehler, gebunden ankb_versionstatt an ein Release-Datum: unterhalb 4.0.0 beratend, ab 4.0.0 hart. Das ist der Unterschied zum Weg vonlegacy_citation_markers, der in einer späteren Version umgelegt wurde und jede Instanz mit offener Migration auf rotes Lint setzte. Das Ledger kann die Frage inzwischen beantworten, also tut es das.Dazu die zwei Befunde des Migrationslaufs, die den Katalog betrafen:
outbound:ist an das Type-Spec gebunden (docs verifyprüft die Kombination), undcomposition/part-ofist das dritte Inversenpaar. Details im Abschlusskommentar.Akzeptanzkriterien
tools/—sections.pyundPRE_CONVENTIONS_NAMESexistieren nicht mehrrelated:trägt Label als Enum-Wert;lintprüft sie gegen dieoutbound:-Autorisierung der Quellcollectionxref addschreibt genau eine Kante; die eingehende Sicht ist gerendert, nicht gespeichertmigrate verifyinstructions/link-taxonomy.mdenthält alle 26 Commonplace-Labels plus die neun operativenmigrate statustrenntrequiredvonofferedund blockiert nie aufofferedmigrate statusmeldet, welche instanzeigenen Dateien von den Stamp-Hashes abweichen.templatedocs verifyprüftSTACK_REQUIRED_TYPESinstructions/dev/version-parts.md; Grenzübertritt gesetzt (4.0.0), Migrationsdokument vorhandenlinterzwingt das (nachgereichtes Kriterium aus dem Kommentar vom 2026-09-02)Vorgeschichte
Aus #39 heraus, Sitzung vom 2026-09-02. Verworfen wurden unterwegs: eine Slot-Tabelle im Type-Spec plus Rendering-Datei in der Instanz (Kompensation für eine Ownership-Grenze an der falschen Stelle),
kb/TEMPLATES.mdals eigene Instanzdatei (dito), und eine Anzeigetabelle für lokalisierte Label (unnötig, sobald der Enum-Wert selbst angezeigt wird).Ausgelagert
#43 — dem Katalog fehlt ein Register für Urheberschaft.
kind/decision,prio/waiting: der Korpus ist konsistent, alle betroffenen Kanten stehen aufsee-also, und der Auslöser ist das nächste Auftreten.Korrektur zur Reihenfolge. Der Satz "Reihenfolge: 4 vor 3" unter Umsetzung stammt aus der Nummerierung des Vorgesprächs und widerspricht der Nummerierung dieses Issues. Gemeint ist, benannt statt nummeriert:
obligation:) — zuerst, weil sie der Träger für alles Folgende sind.types/teilt sich entlangroot:— die Übergabe an die Instanz ist ohne diesen Träger unumkehrbar.Also: Abschnitt 1 → Abschnitt 4 → Abschnitte 2+3.
Zwischenstand: Abschnitte 1 und 4 stehen
Noch nicht publiziert,
VERSIONnoch auf3.0.0— der Bump kommt am Ende des Pakets.Vorbedingung geprüft, sie hält
Die im Issue offene Frage — löst der Provenance-Pfad über den Typ oder über Pfad/Präfix auf? — ist beantwortet:
source_pages_by_raw_file,legacy_source_pagesundduplicate_raw_file_ownersfragen allepage.kind != "source", also dasname:des Type-Specs. Die einzige Kopplung an eine Zeichenkette warprovenance._SOURCE_TITLE_PREFIX, und die ist jetzt aufgelöst (liesttitle_prefix:aus dem Type-Spec, mit dem alten Literal als Fallback für einen Baum ohne auflösbarensource-Typ — sonst würden bestehende Cite-IDs wandern).Die Ankermenge bleibt also so klein wie im Issue beschrieben.
Abschnitt 1 —
obligation:required|offered, Defaultrequired.chain()zählt nurrequired;offers()gibt den Rest.Eine Lücke, die ich beim Testen gefunden habe und die im Issue noch falsch stand: ich hatte Offers gegen
kb_versiongefiltert. Das ist falsch — eine angenommene Offer bewegtkb_versiongerade nicht, also kann die Version nichts darüber aussagen, ob sie angenommen wurde, und ein unbeteiligter Pflicht-Schritt hätte jede Offer stillschweigend verschwinden lassen. Offers sind jetzt gegen das Applied-Ledger gebunden, undmigrate done <offer>schreibt einen Eintrag dorthin, ohne die Version zu bewegen und ohne Reihenfolgeregel — eine Offer ist kein Glied der Kette, also gibt es nichts zu überspringen.Die Erkennungshälfte ist aktiviert:
kb_state.divergent_files()liest die sha256 pro Datei aus.wikitool-release.jsonund beantwortet „editiert oder nur empfangen".Nonestatt[]ohne Stamp — ein Entwicklungsbaum hat keinen, und „nichts abgewichen" wäre dort eine erfundene Antwort.Abschnitt 4 —
types/entlangroot:dist exportschlüsselt die vierroot: kb-Type-Specs samt ihrer.schema.yamlals.templateum;instruction,lint-reportundtype-specgehen verbatim. Abgeleitet austypes/, nicht gelistet — ein eigener Page-Typ der Instanz bekommt dieselbe Behandlung ohne Codeänderung.find_leaksverweigert die gefüllten Namen.STACK_REQUIRED_COLLECTIONSist als separate Liste weg:stack_required_collections()leitet sie aus dembase_dirdessource-Typs ab. Eine Instanz, diekb/sources/umbenennt und das im Type-Spec sagt, bleibt konsistent statt gegen eine hartkodierte Zeichenkette zu laufen.docs verifyprüft neuSTACK_REQUIRED_TYPES: es muss einen Type-Specname: sourcegeben, dessen Schemaraw_filesfordert. Mehr nicht.Damit ist die deutsche Prosa in den vier Page-Type-Specs erledigt — kein Übersetzungsschritt, keine Migrationsschuld. Verifiziert am vollen Replay: Export → frische englische Instanz →
doctor/docs verifygrün,new entityscaffoldet## Relationships/## See Alsoaus der eigenenkb/CONVENTIONS.md, und die## Beschreibung-Überschriften stehen in einer Datei, die dieser Instanz gehört und die sie übersetzen darf.838 Tests grün.
Als nächstes
Abschnitte 2+3 als eine Korpus-Migration: Label werden Enum, Kanten direktional, toolgeführte Blöcke werden generierte Regionen mit Markern.
Korrektur: die Migrationsabbildung im Issue-Text stimmt nicht
Der Abschnitt Die Taxonomie behauptet eine Abbildung „1:1 bis auf eine Zusammenlegung" von dreizehn Labeln. Gemessen am Korpus ist das falsch, und zwar deutlich.
152 distinkte Beziehungslabel in 337 Bullets.
kb/CONVENTIONS.mdlistet dreizehn; nichts hat je geprüft, ob sie eingehalten werden, und sie wurden es nicht. Die Top 20 decken 167 der 337 Bullets ab, 102 Label kommen genau einmal vor (Erfand,Gegenmittel zu,spiegelt das gleiche Muster wie,würde implementiert von, …). Die dokumentierte Liste deckt grob ein Sechstel.Dazu ein Anteil Gegenrichtungen, den ich unterschätzt hatte:
Verwendet von(20×),umgesetzt von,implementiert durch,Ersetzt durch,Erweitert durch. Unter dem direktionalen Modell sind das genau die Kanten, die nicht mehr gespeichert, sondern gerendert werden.Und
## Siehe auch555 nackte
- [[X]]-Bullets ohne Label — mehr als alle gelabelten Beziehungen zusammen. Die Spiegelhälfte der erzwungenen Bidirektionalität.Die 353 fallen mechanisch. Bei den 202 ist mechanisch nicht entscheidbar, ob jemand die Verbindung gemeint hat oder ob
xref addsie als Spiegel schrieb und die gelabelte Hälfte später verloren ging.Folge
migration_kindistassisted, nichtmechanical. Umfang: ~539 Einzelentscheidungen (337 gelabelte Bullets + 202 ungedeckte Siehe-auch-Kanten) über rund 140 Seiten, nachmigrate-corpus.mdalso drei bis vier Einheiten mit je eigenem Publish.Entschieden: redaktionell, nicht mit mechanischem Boden. Die Alternative wäre gewesen, die Top 20 per Tabelle abzubilden und den Rest auf
see-alsozu setzen — das hätte ~370 von ~539 Kanten mit dem schwächsten Label des Katalogs starten lassen, also genau denverwandt mit-Bodensatz reproduziert, den die Umstellung beendet, nur diesmal als dokumentierten Startzustand.Das Akzeptanzkriterium „Ein Label hinzufügen braucht keine Codeänderung" bleibt; neu dazu: die Migration ist erst fertig, wenn kein Bullet mehr ein Label außerhalb des Katalogs trägt und
lintdas erzwingt.Die Mechanik ist davon nicht blockiert und wird zuerst gebaut.
u0 steht — die Mechanik ist vollständig, keine Seite angefasst
856 Tests grün,
docs verify/instructions verify/doctorgrün, keine Hard-Error-Findings im Korpus.Gelöscht, ersatzlos
tools/chemenu/sections.pyexistiert nicht mehr. Mit ihm:heading_re, der Alias-Mechanismus,PRE_CONVENTIONS_NAMES,cite_block_heading,provenance.__getattr__, die{section.*}-Template-Variablen undxrefs Abschnittssuche (_section_bounds,add_bullet_to_section,add_relationship_bullet,add_see_also_bullet). Kein Überschriftentext liegt mehr in Python — bis auf zwei Fallback-Strings inconventions.DEFAULT_HEADINGS, und die sind rein kosmetisch: der Marker trägt die Identität der Region, also rendert ein falscher Default falsche Wörter statt Struktur zu zerlegen, und der nächste Write repariert es.Neu
blocks.py(Marker-Regionen:<!-- wikitool:links -->/<!-- wikitool:footnotes -->),links.py(gelabelte Kanten, liest beide Formen),instructions/link-taxonomy.md(35 Label),outbound:in allen vierCOLLECTION.md,links showals abgeleitete Eingangssicht.Vier Korrekturen am Entwurf, die die Umsetzung erzwungen hat
1.
sections:fällt nicht weg. Das Issue behauptet es. Falsch: die Region rendert ihre Überschrift mit, also braucht sie einen Text. Was wegfällt, ist das Matching — der Wert ist jetzt Anzeige, nicht Identifier, dieselbe Kategorie wielayout: {project: {title: Projekte}}. Von drei Slots auf zwei:linksundfootnotes.2. Es gibt keine
see-also-Region mehr. Sie war die Spiegelhälfte des bidirektionalenxref add; unter direktionalen Kanten istsee-alsoein Label innerhalb der Links-Region. Zwei Regionen statt drei.3.
index rebuildrendert die Eingangssicht nicht. Ich hatte das so ins Issue geschrieben und es beim Bauen verworfen: eine Backlink-Liste pro Seite in einen Shard zu schreiben, der ohnehin eine Seitentabelle ist, verdoppelt ihn und hilft niemandem. Stattdessenwikitool links show --page— Ausgangskanten ausrelated:, Eingangskanten über den Korpus berechnet, budgetbefreit wiesearch. Dazu der Punkt, den ich vorher übersehen hatte: das gerenderte Bullet- **depends-on:** [[Hermes]]ist ein Wikilink, also zeigt Obsidians Backlink-Panel die Eingangssicht für einen menschlichen Leser bereits von selbst.4. Der Orphan-Check meldet jetzt mehr. Direktionale Kanten heißen: eine Seite, die nur nach außen zeigt, ist tatsächlich unerreichbar. Das ist die Prüfung, die ihre Arbeit tut — sie misst Erreichbarkeit statt "ist
xrefgelaufen" —, aber es ist eine Verhaltensänderung, die u1–u4 sichtbar machen werden.Lint-Schärfe
malformed_edgesundunbalanced_markerssind hart ab sofort: keines beschreibt eine unkonvertierte Seite, nur eine kaputte.unlabelled_edgesundunauthorised_labelssind advisory bis zum Abschluss der Migration — sie beschreiben exakt das Fenster, für das.wikitool-kb.jsonexistiert. Die Beförderung zu Hard Errors ist Teil von u5, derselbe Weg, denlegacy_citation_markersgenommen hat.Die Zahl für u1–u4
480, nicht 337.
related:trägt mehr Einträge als der Body gelabelte Bullets hat — Frontmatter und Body waren unter dem alten Modell längst auseinandergelaufen, und niemand konnte es sehen, solange das Label nur in der Prosa stand. Das ist selbst ein Befund.Plan, Messwerte und die getroffenen Entscheidungen liegen in
work/link-taxonomy-migration/. Noch nicht publiziert;VERSIONsteht weiter auf3.0.0, der Bump gehört nach u5.u0 publiziert als
4.0.0—177c7e956 Dateien, +2.695/-753.
VERSIONauf4.0.0, Migrationsdokumentinstructions/migrations/4.0.0-link-taxonomy.md(assisted,required), Baum sauber.Dieser Commit ist die Baseline für u1–u4.
migrate verify --from 177c7e9vergleicht gegen einen Stand, in dem die Marker-Maschinerie existiert und noch keine Seite angefasst ist — von hier aus misst jede Einheit genau ihre eigene Arbeit.Warum der Bump vor der Korpusarbeit kam statt danach, wie ursprünglich geplant: CIs Version-Gate verweigert eine Stack-Änderung ohne Versionsbewegung, und u0 hat
tools/,types/,instructions/und vier Contracts angefasst. Die Einheiten u1–u4 fassen nurkb/an, brauchen also keinen weiteren Bump — und werden von CI ohnehin überpaths-ignoreausgeschlossen.Stand der Akzeptanzkriterien
tools/—sections.pyundPRE_CONVENTIONS_NAMESexistieren nicht mehrrelated:trägt Label als Enum-Wert;lintprüft gegenoutbound:der Quellcollectionxref addschreibt genau eine Kante; die eingehende Sicht ist gerendert (links show), nicht gespeichertmigrate verifyinstructions/link-taxonomy.mdenthält alle 26 Commonplace-Label plus die neun operativenmigrate statustrenntrequiredvonofferedund blockiert nie aufofferedmigrate statusmeldet, welche instanzeigenen Dateien von den Stamp-Hashes abweichen.templatedocs verifyprüftSTACK_REQUIRED_TYPESversion-parts.md, Grenzübertritt gesetzt, Migrationsdokument vorhandenOffen bleibt allein der Bestand: 480 ungelabelte Kanten über u1–u4, danach u5 (Beförderung der beiden Lint-Findings zu Hard Errors,
migrate done 4.0.0, Workshop schließen).Korpus-Migration abgeschlossen (Abschnitte 2 und 3)
Die Korpus-Hälfte dieses Issues ist durch.
kb_versionsteht auf4.0.0,lint --fail-on-errorunddocs verifysind grün, der Workshopwork/link-taxonomy-migration/ist geschlossen und gelöscht.Umfang: 152 Seiten, ~530 gelabelte Kanten, in fünf Einheiten über mehrere Sitzungen:
kb/entities/systems, tools, technologies — 57 Seiten, 199 Kantenb137359kb/entities/projects, people +kb/comparisons/— 16 Seiten, ~45 Kanten9e2f9bfkb/concepts/— 80 Seiten, ~250 Kantena35c94ekb/sources/— 0 Seiten (siehe unten)migrate done 4.0.0, Workshop-Close3f99d67Endstand
lint: 0unlabelled_edges, 0unauthorised_labels, 0malformed_edges, 0unbalanced_markers, 0broken_links, 0dangling_frontmatter_refs, 0schema_validation_errors. Damit ist Schritt 6 des Migrationsdokuments erfüllt — die beiden Findings können jetzt zu harten Fehlern promoted werden (nicht Teil dieses Laufs, siehe unten).Drei Befunde, die im Entwurf nicht standen
1.
comparisonundsourcehaben gar keinrelated:-Feld. Beide Type-Specs deklarieren nurpage_ref_fields: [entities]bzw.[entities, concepts];xref addverweigert dort. Dieoutbound:-Blöcke inkb/comparisons/COLLECTION.md(compares-with,contrasts,see-also) undkb/sources/COLLECTION.md(is-evidence-for,defined-in,see-also) autorisieren damit Label, die auf diesen Seiten strukturell nicht geschrieben werden können — die Autorisierung läuft ins Leere. Fürkb/sources/war das folgenlos (entities:/concepts:waren beim Ingest überxref link-sourcekorrekt gefüllt,migrate verify --path kb/sourcesmeldet 0 Befunde gegen die Baseline); beikb/comparisons/wurde das Label per Hand in die Prosa geschrieben. Entweder sollten die beiden Type-Specs einrelated:bekommen, oder dieoutbound:-Blöcke dort sollten weg — im jetzigen Zustand behauptet der Contract eine Fähigkeit, die es nicht gibt.2. Dem Katalog fehlt ein Register für Urheberschaft. Kein Label trägt „Person hat X erschaffen" oder „X baut auf der Arbeit von Y auf".
ownsdeckt fortlaufende Verantwortlichkeit ab (so benutzt fürVannevar Bush owns Memex,Rohit Gupta owns Agent Memory,E3DC GmbH owns E3DC), aber der einmalige historische Akt — Karpathy formuliert das LLM Wiki Pattern, Gupta erweitert Karpathys Arbeit — hat in Realization und Lineage nichts, was für eine Person autorisiert wäre: die Register sind concept↔concept oder concept↔entity, nie entity↔entity oder entity↔concept für Autorschaft. Alle diese Kanten stehen jetzt aufsee-also, nach Prüfung jedes einzelnen Registers. Kandidat für eine Katalog-Zeile (originates?authored-by?), falls das wiederkehrt.3.
part-of/compositionverhalten sich in der Praxis wie ein Inversenpaar, sind aber keins. Der Katalog nennt ausdrücklich nurdepends-on/required-byundruns-on/hostsals Paare mit eigener Gegenaussage. Bei Eltern-Kind-Strukturen (Consolidation Tiers und seine vier Ebenen, Implementation Spectrum und seine Level, Multi-Agent Collaboration und seine drei Komponenten) entsteht dadurch regelmäßig die Frage, ob die Gegenseitepart-ofschreiben darf, wenn das Elternteil schoncompositionhat. Regel im Lauf: nein — die zweite Seite bekommtsee-also. Das erzeugt sichtbar mehrsee-also, als der Entwurf wollte; ob das die richtige Auflösung ist, wäre eine bewusste Katalog-Entscheidung wert.Zwei Fehler im Lauf, beide selbst gefunden
lint --fullsunlabelled_edges-Bericht nach dem ersten u3-Durchlauf.Personalization Plane.mdentfernte ich eine inhaltlich eigenständige, mit Fußnote belegte Zeile mit, weil ich sie für ein bloßes Wikilink-Bullet hielt.migrate verifyfing das übercite-refs 2->1— der Marker-Paar- und Zähl-Invariant aus Abschnitt 3 dieses Issues hat also im ersten Ernstfall genau das getan, wofür er gebaut wurde.Dazu eine Selbstkorrektur: die Asymmetrie „
xref addschreibt nur A,xref removeräumt beide Seiten" habe ich in u3 als Erkenntnis notiert — sie steht wörtlich intools/CONTRACT.mdZeilen 43–44. Sie war nachzulesen, nicht zu entdecken; in u1 kostete das mehrere unnötig defensive Entscheidungen (die 37 Restkanten in u5 sind genau diese Vorsicht).Was an diesem Issue offen bleibt
Die Abschnitte 1 und 4 sind unberührt:
obligation: offered, der Stamp-Hash-Abgleich inmigrate status, die Aufteilung vontypes/entlangroot:undSTACK_REQUIRED_TYPES. Ebenso offen die Promotion vonunlabelled_edges/unauthorised_labelszu harten Fehlern — der Korpus erfüllt sie jetzt, der Schritt selbst ist eine eigene Stack-Änderung.torben referenced this issue2026-09-03 01:49:08 +00:00
Changelog: Body auf den Abschlussstand gezogen. Neu: Status-Zeile, Abschnitt 5. Abschluss (4.1.0), alle Kriterien abgehakt, das nachgereichte zwölfte Kriterium aus dem Kommentar vom 2026-09-02 aufgenommen, Abschnitt Ausgelagert mit #43. Korrigiert: die Reihenfolge steht jetzt benannt im Body statt nur im ersten Kommentar; die Inversenpaare sind drei statt zwei; die falsche 1:1-Migrationsabbildung ist durch die Messung ersetzt; die vier Umsetzungskorrekturen aus dem u0-Kommentar stehen im Body statt nur in der Historie. Entfernt: die widerlegte Behauptung,
sections:falle weg undindex rebuildrendere die Eingangssicht.Abschluss
Die letzten drei offenen Punkte sind als
4.1.0publiziert, Commitcfe925a. Damit ist dieses Issue vollständig.Was in diesem Lauf umgesetzt wurde
Die Beförderung der beiden Lint-Findings — aber gebunden statt umgelegt. Der Entwurf sagte "werden hart, nachdem die Migration aufgezeichnet ist", und das wäre als Konstante im Code genau der Weg von
legacy_citation_markersgewesen: in einer späteren Version umgelegt, und jede Instanz, die die Migration noch schuldete, lebte danach mit rotem Lint. Für 4.0.0 wäre das schlimmer gewesen, weilmigrate-corpus.mdeinen Lauf in Einheiten mit je eigenem Publish vorschreibt — einlint --fail-on-error, das dabei fehlschlägt, verweigert genau den Korpus, dessen Fortschritt es misst.Also
hard_error_keys()gegenkb_version: unterhalb 4.0.0 beratend, ab 4.0.0 hart, ohne Stamp-Datei bleibt alles hart (eine frische Instanz startet in der aktuellen Form, statt in sie hineinzumigrieren). Das ist der Punkt, an dem Abschnitt 1 dieses Issues sich auszahlt — das Ledger kann die Frage inzwischen beantworten, also muss keine Datumskonstante sie raten.HARD_ERROR_KEYSbleibt die vollständige Menge,hard_error_keys()liefert die jeweils geltende.Befund 1 — und er war folgenreicher als im Abschlusskommentar notiert. Dass
kb/comparisons/undkb/sources/Label autorisierten, die ihre Type-Specs strukturell nicht schreiben können, blieb nicht folgenlos: die einzige Comparison-Seite des Korpus trugals handgeschriebene Prosa — ohne Marker-Region, ohne Frontmatter-Deckung, für
lintunsichtbar, weilcomparisonkeinrelated:führte. Also ein Identifier zurück im Fließtext, geschrieben in u2, wenige Stunden nach dem Commit, der genau das beendet hatte. Eine leere Autorisierung liest sich als Lizenz; wer sie liest und keinen Träger findet, schreibt das Label in die Prosa.Aufgelöst nach dem, was die beiden Contracts jeweils selbst sagen — asymmetrisch, weil die beiden Fälle es sind:
comparisonrelated:inpage_ref_fieldsund Schemacompares-with-Kante gegen jedes Subjekt ist die eine Aussage, für die die Seite existiert;entities:ist die untypisierte Liste danebenkb/sources/outbound:-Block ersatzlosraw_files:,entities:,concepts:,[^cite-id]) und keine AutorenkantenDazu die Prüfung, die die beiden Hälften künftig zusammenhält:
docs verifymeldet einenoutbound:-Block auf einer Collection, in die kein Typ mitrelated:schreibt, und nennt beide Richtungen der Reparatur. Die Comparison-Seite trägt ihre zwei Kanten jetzt als deklarierte Kanten in einerwikitool:links-Region.Befund 3 —
composition/part-ofist das dritte Inversenpaar. Die u3-Regel ("die Gegenseite bekommtsee-also, weilpart-ofein Spiegel wäre") ist zurückgenommen: der Satz des Elternteils zählt seine Teile auf, der des Kindes benennt das Ganze, zu dem es gehört. Zwei Sätze, nicht einer zweimal. 16see-also-Kanten sind aufpart-ofrelabelt — Consolidation Tiers, Content Quality Control, Hybrid Search, Knowledge Graph, LLM Wiki Pattern, Memory Lifecycle, Multi-Agent Collaboration, Wine GE, farzaa gist und ihre Kinder.Präzisiert dabei, weil es sonst gegen "Richtung wird verfasst, nicht gespiegelt" liefe: ein Inversenpaar macht die Gegenkante nicht zur Pflicht. Es legt fest, welches Label sie trägt, wenn jemand sie schreibt. Zehn
composition-Kanten haben weiterhin keine Gegenkante, und das ist kein Befund — vor allem die sechs Ebenen des Implementation Spectrum, deren Zugehörigkeit zu einem Spektrum eine schwächere Aussage ist als die Teil-Ganzes-Beziehung, diepart-ofbehauptet.Was bewusst nicht umgesetzt wurde
Befund 2, das fehlende Register für Urheberschaft — ausgelagert nach #43,
kind/decision/prio/waiting. Es erweitert den Katalog, statt einen Widerspruch zu beheben, und der Zuschnitt (eigenes Register oder Lineage? ein Label oder zwei? Inversenpaar?) lässt sich an drei Fällen nur raten. Der Korpus ist in der Zwischenzeit konsistent: alle betroffenen Kanten stehen aufsee-also, keine trägt ein falsches Label.Der Versionsteil war eine Abwägung, keine Formsache. Der neue
docs verify-Check hätte eine bestehende 4.0.x-Instanz beim bloßen Kopieren der Maschinerie fehlschlagen lassen, weil ihrekb/sources/COLLECTION.mddenoutbound:-Block noch trägt — nachversion-parts.mdSchritt 1 ein Grenzübertritt. Erwogen und verworfen wurden eine5.0.0mit Pflichtmigration (zwei Zeilen löschen, dafür zahlt jede Instanz einen Major) und die Abstufung des Checks auf einendoctor-WARN (Drop-in, aber die Prüfung wäre schwächer als der Befund, den sie gefunden hat). Stattdessen ist die Ursache mitbeseitigt: die Collection-Contracts dieser Instanz sind angepasst,dist exportleitet dieCOLLECTION.md.templatedaraus ab, also liefert jede neue Distribution die korrigierte Form aus. Für eine bereits bestehende 4.0.x-Instanz bleibt eine Handbewegung, und sie steht namentlich inCHANGES.mdstatt versteckt zu sein.Nebenbefund, eigenes Issue
#44 — die
kb_dir-Fixture der Testsuite repointetconfig.ROOTnicht. Aufgefallen beim Bau der Gate-Tests: ein Test überschrieb das echte.wikitool-kb.jsondes Repos (zurückgeholt, Tests auf eine lokalerooted_kb-Fixture umgestellt). Die stillere Hälfte wiegt schwerer —lint_core._collection_of()löst gegenconfig.KB_DIRstatt gegen den übergebenen Baum auf, weshalbunauthorised_labelsin der Suite bis 4.1.0 faktisch nicht getestet war.Verifiziert
863 Tests,
docs verify,instructions verify,doctor,lint --fail-on-errorunddist exportgrün.migrate verify --from HEAD: 181 Seiten verglichen, 0 hinzugefügt, 0 entfernt, 18 Befunde — sämtlich Label-Wechsel im Frontmatter, keine Änderung an Wikilink- oder Zitatzahlen, keine Marker-Paar-Differenz.Anschluss laut #39: #28, danach #30.