Prosa ist kein Identifier: Link-Taxonomie als Enum, toolgeführte Blöcke als generierte Regionen, types/ nach Eigentum #40

Closed
opened 2026-09-02 15:40:56 +00:00 by torben · 7 comments
Owner

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 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:

  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

  • 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.
torben added the prio/blockingsize/L labels 2026-09-02 15:41:16 +00:00
Author
Owner

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 direktionaltoolgefü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.
Author
Owner

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.

## 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.
Author
Owner

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.
Author
Owner

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.

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.

## 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.
Author
Owner

u0 publiziert als 4.0.0177c7e9

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).
Author
Owner

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 --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.
torben added the area/kbkind/build labels 2026-09-02 21:24:38 +00:00
Author
Owner

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.

**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**.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: torben/chemenu#40