Status: umgesetzt und ausgeliefert in 3.0.0 (Commit 502971d, „Autorenkonventionen nach Eigentum geschnitten"). Alle Akzeptanzkriterien unten sind abgehakt und am 2026-09-03 nachverifiziert. Der Anschluss ist unten unter Nach Abschluss geht es weiter bei benannt.
Der Befund
kb/CONTRACT.md ist eine Datei mit zwei Autoritäten. Ein Teil ist code-erzwungen und darf nie abweichen; ein Teil ist Konvention, die jede Instanz für sich entscheidet — und wird trotzdem als bindender Contract ausgeliefert.
§ Language komplett, Beziehungslabel-Vokabular (hängt ab von, verwendet, …), Ton-Beispiele und Buzzword-Liste (deutsch), Confidence-Rubrik (0.5 / +0.2 pro Quelle / …), ADR-Präfix
Die Sprachentscheidung ist der Extremfall: sie steckt nicht in einer Datei, sondern in vier Schichten.
setup-instance.md Schritt 5 wusste das und löste es als Handarbeit an fünf Stellen über drei Schichten.
Warum das mehr war als Unordnung
Zwei Dokumente widersprachen sich für jede nicht-deutsche Instanz. Wer bei Schritt 5 „Englisch" antwortete, hatte lokal kb/CONTRACT.md, vier Type-Specs und sections.py geändert. private-instance.mds Decision Point sagt für so einen Merge-Konflikt: „Conflict in tools/, types/ or instructions/? You changed the stack locally — take the upstream side and re-file the change as an issue there." Die Upstream-Seite nehmen hieß für diese Instanz: KB-Sprache zurück auf Deutsch.
Und die Merge-Prozedur aus 2.2.1 hatte dadurch ein zweites Leck, in die andere Richtung als das in #30 beschriebene. git checkout HEAD -- kb raw holt alles unter beiden Stages auf den Vor-Merge-Stand — auch diese sechs Maschinerie-Dateien:
Änderte der Upstream einen davon, warf die Prozedur das Update still weg. Die Kontrollzeile git diff --name-only $BEFORE HEAD -- kb raw meldete dann leer, also „nachweislich funktioniert", während sie gerade eine Contract-Änderung gefressen hatte. Die Kontrolle, die der wichtigste Teil sein soll, bestätigte hier den Fehler.
dist_cmd.py kannte die Unterscheidung bereits (CONTRACT_ONLY_STAGES plus jedes kb/*/COLLECTION.md werden explizit kopiert). Die Prosa-Prozedur hatte sie nicht.
Vorbild: Commonplace
Gemessen an commonplace/ (vendored, siehe instructions/dev/commonplace-kb.md), drei zusammengehörige Mechanismen:
Die Autoritätsrichtung ist umgedreht. Der Profil-Katalog sagt über sich selbst: „This page is that library: a palette, not an enum. Each COLLECTION.md stays authoritative for the collection's actual contract; a profile entry here is a proven starting point, not a binding source." Das Ausgelieferte ist unverbindliche Referenz, das Instanz-eigene bindet. Bei uns war es andersherum.
Init scaffoldet Templates mit Entscheidungsprompt („theoretical / descriptive / prescriptive — pick one"), nicht fertigen Inhalt zum Zurückeditieren (ADR-021).
Versionsmarker + Drift-Check an der Eigentumsgrenze (.commonplace): bei Drift wird nicht überschrieben, sondern verweigert.
Dazu der Begriff, der die Diagnose trägt: system-definition artifact (bindet) vs. knowledge artifact (informiert).
Disanalogie, die die Kopie begrenzt: ADR-021s Namespace (kb/commonplace/) löst „ausgelieferter Inhalt kollidiert mit Nutzerinhalt" — 195 Methodik-Notizen im kb/notes/ des Nutzers. Chemenu liefert null Seiten aus. Unser Problem war nicht ausgelieferter Inhalt, sondern ausgelieferter Contract. Der Namespace war deshalb die falsche Hälfte; die richtige sind Punkt 1 und 2.
Entscheidung
Der Schnitt lief bis dahin nach Ort — alles über kb/ steht in kb/CONTRACT.md. Er läuft seit 3.0.0 danach, wer den Satz ändern darf.
Datei
Bindet
Besitzt
Distribution
Im Merge
kb/CONTRACT.md
ja
Stack
Datei, verbatim — nur noch das Code-Erzwungene
muss mergen
kb/CONVENTIONS.md
ja
Instanz
.template
nie im Merge
kb/*/COLLECTION.md
ja
Instanz
.template je Default-Collection, adoptiert ein Profil per Referenz
–
instructions/kb-profiles.md
nein
Stack
Datei — Palette: die heutigen vier Collection-Contracts plus das deutsche Sprachprofil als erprobte Bündel
mergt folgenlos
kb/CONVENTIONS.md trägt: KB-Sprache samt Abschnittsnamen, Beziehungslabel-Vokabular, Ton-Beispiele und Buzzword-Liste, Confidence-Rubrik, ADR-Präfix. Die Distribution liefert nur kb/CONVENTIONS.md.template — exakt der USER.md/SOUL.md-Split, den ROOT_FILES und doctors Sentinel-Check schon konnten. Kein neuer Mechanismus, ein vorhandener ein Verzeichnis tiefer.
COLLECTION.md bekam Frontmatter
Bis dahin wurde eine Collection rein an der Dateipräsenz erkannt (kb_collections.py). Die Deklaration brauchte einen Träger, sonst hätten wir den Ortsschnitt durch einen Prosaschnitt ersetzt:
profile:<name aus instructions/kb-profiles.md>required_by_stack:true|false
required_by_stack: true heißt: Autorenkonventionen darf die Instanz umschreiben, löschen oder umbenennen nicht. Das trifft sources (sources coverage, Cite-Auflösung, provenance.md), nicht entities. Geprüft von docs verify.
Nachtrag aus 4.0.0: required_by_stack: wird nicht auf Treu und Glauben genommen, sondern gegen stack_required_collections() geprüft, das die Menge aus dem base_dir: des source-Type-Specs ableitet. Eine separate Liste im Code gibt es nicht.
Die Stelle, an der es sonst Prosa-Umschichtung geblieben wäre
RELATIONSHIPS = "Beziehungen" in sections.py war der Punkt, an dem die Konvention in Code übergelaufen war. Solange sie dort stand, konnte kein Template die Sprache umstellen. Das Modul las die drei Namen ab 3.0.0 aus den Instanz-Konventionen; der Alias-Mechanismus war der Migrationspfad. In 4.0.0 (#40) ist sections.py ersatzlos entfallen — Marker-Regionen tragen die Identität, der Überschriftentext ist reine Anzeige.
Verworfen: sources/ aus kb/ herausziehen
Erwogen, weil sources sich strukturell von entities unterscheidet. Verworfen, gemessen:
Der Graph ist einwurzelig.kb_scan.iter_kb_pages(kb_dir) macht ein kb_dir.rglob("*.md"); darauf sitzen build_link_graph, inbound_links, find_duplicate_title_paths, Orphan-Erkennung, index rebuild, search. Source-Seiten sind der dichteste Knotentyp darin — jedes [^cite-id] löst gegen eine auf. Ein Hoist macht jede Graph-Operation dauerhaft zweiwurzelig, um ein Verzeichnis umzubenennen.
kb_collections erzwingt „kein COLLECTION.md außerhalb kb/". Ein Top-Level-sources/ verlöre entweder seinen Contract oder diese Invariante bekäme eine Ausnahme.
Der Ort kann Eigentum ohnehin nicht kodieren, sobald Collections offen sind (kb_collections.py: „mkdir kb/<name> && $EDITOR kb/<name>/COLLECTION.md add a collection without a code change"; types/type-spec.md:45: „Adding a type must require no code change"). Eine selbst angelegte Collection liegt im selben kb/ wie die Defaults. Eigentum ist eine deklarierte, keine Orts-Eigenschaft — daher das Frontmatter oben.
Sichtbar halten, ohne es zu nehmen: types/type-spec.md kennt bereits root: kb | repo. Eine Seite außerhalb kb/ ist schema-seitig vorgesehen; es fehlt der zweiwurzelige Graph-Walker. Wenn je eine Stage außerhalb kb/ Seiten tragen soll, ist das die Stelle — kein Sonderfall für sources.
Umsetzung
kb/CONTRACT.md schneiden; kb/CONVENTIONS.md + .template; Eintrag in dist_cmd.py; doctor-Check analog zum Personalization-Sentinel.
COLLECTION.md-Frontmatter (profile:, required_by_stack:) in kb_collections.py + docs verify; die vier Default-Contracts als Templates.
sections.py und die vier Type-Specs von den hartkodierten deutschen Namen lösen.
instructions/kb-profiles.md schreiben; setup-instance.md Schritt 5 auf „Profil wählen" umstellen; private-instance.mds Decision Point korrigieren; Version-Bump.
Akzeptanzkriterien
Kein Satz in kb/CONTRACT.md, den eine Instanz ändern müsste
kb/CONVENTIONS.md ist nicht im Distributions-Dateiplan; nur das Template ist es
doctor FAILt auf fehlende CONVENTIONS.md und auf eine mit Template-Sentinel
docs verify prüft profile: und required_by_stack: auf jedem COLLECTION.md
Eine englischsprachige Instanz ändert keine Datei unter tools/ oder types/
setup-instance.md Schritt 5 nennt eine Entscheidung, keine fünf Editierstellen
private-instance.mds Decision Point widerspricht dem nicht mehr
Version-Bump erfolgt nach instructions/dev/version-parts.md; der Verdacht auf Boundary-Crossing hat sich bestätigt und wurde als 3.0.0 mit Migrationsdokument gelöst
Vorgeschichte
Entschieden in der Sitzung vom 2026-09-02, aus #30 heraus. Die Alternativen (/sources-Hoist, Marker-Blöcke im bestehenden Text, nur die Pfadmenge maschinenlesbar machen) sind oben mit Begründung verworfen.
Nach Abschluss geht es weiter bei
#28 — der kuratierte Fixture-Korpus. Er muss gegen die hier entschiedene Contract-Form autorisiert werden; vorher geschrieben, wird er zweimal geschrieben. Danach #30.
> **Status: umgesetzt und ausgeliefert in `3.0.0`** (Commit `502971d`, „Autorenkonventionen nach Eigentum geschnitten"). Alle Akzeptanzkriterien unten sind abgehakt und am 2026-09-03 nachverifiziert. Der Anschluss ist unten unter *Nach Abschluss geht es weiter bei* benannt.
## Der Befund
`kb/CONTRACT.md` ist eine Datei mit zwei Autoritäten. Ein Teil ist code-erzwungen und darf nie abweichen; ein Teil ist Konvention, die jede Instanz für sich entscheidet — und wird trotzdem als bindender Contract ausgeliefert.
| Ebene | Beispiele |
|---|---|
| Code-erzwungen | Collections-Regeln (`docs verify`), Generated-Files-Tabelle, `provenance:`-Enum, Cite-/Footnote-Mechanik, `confidence_base` vs. abgeleitetes `confidence`, Orphan-Check, Blockquote-Cap |
| Instanz-Konvention | **§ Language komplett**, Beziehungslabel-Vokabular (`hängt ab von`, `verwendet`, …), Ton-Beispiele und Buzzword-Liste (deutsch), Confidence-Rubrik (0.5 / +0.2 pro Quelle / …), ADR-Präfix |
Die Sprachentscheidung ist der Extremfall: sie steckt nicht in einer Datei, sondern in vier Schichten.
```
kb/CONTRACT.md § Language + § Linking (Labels) + § Tone (Beispiele)
types/{entity,concept,source,comparison}.md → "## Beziehungen", "## Siehe auch", "## Fußnoten"
tools/chemenu/sections.py → RELATIONSHIPS = "Beziehungen" ← Python-Quelltext
instructions/german-terminology.md → ganze Datei
```
`setup-instance.md` Schritt 5 wusste das und löste es als **Handarbeit an fünf Stellen über drei Schichten**.
## Warum das mehr war als Unordnung
**Zwei Dokumente widersprachen sich für jede nicht-deutsche Instanz.** Wer bei Schritt 5 „Englisch" antwortete, hatte lokal `kb/CONTRACT.md`, vier Type-Specs **und `sections.py`** geändert. `private-instance.md`s Decision Point sagt für so einen Merge-Konflikt: *„Conflict in `tools/`, `types/` or `instructions/`? You changed the stack locally — take the upstream side and re-file the change as an issue there."* Die Upstream-Seite nehmen hieß für diese Instanz: KB-Sprache zurück auf Deutsch.
**Und die Merge-Prozedur aus 2.2.1 hatte dadurch ein zweites Leck**, in die andere Richtung als das in #30 beschriebene. `git checkout HEAD -- kb raw` holt *alles* unter beiden Stages auf den Vor-Merge-Stand — auch diese sechs Maschinerie-Dateien:
```
kb/CONTRACT.md kb/{comparisons,concepts,entities,sources}/COLLECTION.md raw/CONTRACT.md
```
Änderte der Upstream einen davon, warf die Prozedur das Update still weg. Die Kontrollzeile `git diff --name-only $BEFORE HEAD -- kb raw` meldete dann *leer*, also „nachweislich funktioniert", während sie gerade eine Contract-Änderung gefressen hatte. Die Kontrolle, die der wichtigste Teil sein soll, bestätigte hier den Fehler.
`dist_cmd.py` kannte die Unterscheidung bereits (`CONTRACT_ONLY_STAGES` plus jedes `kb/*/COLLECTION.md` werden explizit kopiert). Die Prosa-Prozedur hatte sie nicht.
## Vorbild: Commonplace
Gemessen an `commonplace/` (vendored, siehe `instructions/dev/commonplace-kb.md`), drei zusammengehörige Mechanismen:
1. **Die Autoritätsrichtung ist umgedreht.** Der Profil-Katalog sagt über sich selbst: *„This page is that library: a palette, not an enum. Each `COLLECTION.md` stays authoritative for the collection's actual contract; a profile entry here is a proven starting point, not a binding source."* Das Ausgelieferte ist unverbindliche Referenz, das Instanz-eigene bindet. Bei uns war es andersherum.
2. **Init scaffoldet Templates mit Entscheidungsprompt** („theoretical / descriptive / prescriptive — pick one"), nicht fertigen Inhalt zum Zurückeditieren (ADR-021).
3. **Versionsmarker + Drift-Check** an der Eigentumsgrenze (`.commonplace`): bei Drift wird nicht überschrieben, sondern verweigert.
Dazu der Begriff, der die Diagnose trägt: **system-definition artifact** (bindet) vs. **knowledge artifact** (informiert).
**Disanalogie, die die Kopie begrenzt:** ADR-021s Namespace (`kb/commonplace/`) löst *„ausgelieferter Inhalt kollidiert mit Nutzerinhalt"* — 195 Methodik-Notizen im `kb/notes/` des Nutzers. **Chemenu liefert null Seiten aus.** Unser Problem war nicht ausgelieferter *Inhalt*, sondern ausgelieferter *Contract*. Der Namespace war deshalb die falsche Hälfte; die richtige sind Punkt 1 und 2.
## Entscheidung
Der Schnitt lief bis dahin **nach Ort** — alles über `kb/` steht in `kb/CONTRACT.md`. Er läuft seit 3.0.0 **danach, wer den Satz ändern darf**.
| Datei | Bindet | Besitzt | Distribution | Im Merge |
|---|---|---|---|---|
| `kb/CONTRACT.md` | ja | Stack | Datei, verbatim — nur noch das Code-Erzwungene | **muss** mergen |
| `kb/CONVENTIONS.md` | ja | **Instanz** | `.template` | **nie** im Merge |
| `kb/*/COLLECTION.md` | ja | **Instanz** | `.template` je Default-Collection, adoptiert ein Profil per Referenz | – |
| `instructions/kb-profiles.md` | **nein** | Stack | Datei — Palette: die heutigen vier Collection-Contracts plus das deutsche Sprachprofil als erprobte Bündel | mergt folgenlos |
`kb/CONVENTIONS.md` trägt: KB-Sprache samt Abschnittsnamen, Beziehungslabel-Vokabular, Ton-Beispiele und Buzzword-Liste, Confidence-Rubrik, ADR-Präfix. Die Distribution liefert nur `kb/CONVENTIONS.md.template` — **exakt der `USER.md`/`SOUL.md`-Split, den `ROOT_FILES` und `doctor`s Sentinel-Check schon konnten.** Kein neuer Mechanismus, ein vorhandener ein Verzeichnis tiefer.
### `COLLECTION.md` bekam Frontmatter
Bis dahin wurde eine Collection rein an der Dateipräsenz erkannt (`kb_collections.py`). Die Deklaration brauchte einen Träger, sonst hätten wir den Ortsschnitt durch einen Prosaschnitt ersetzt:
```yaml
profile: <name aus instructions/kb-profiles.md>
required_by_stack: true|false
```
`required_by_stack: true` heißt: Autorenkonventionen darf die Instanz umschreiben, löschen oder umbenennen nicht. Das trifft `sources` (`sources coverage`, Cite-Auflösung, `provenance.md`), nicht `entities`. Geprüft von `docs verify`.
Nachtrag aus 4.0.0: `required_by_stack:` wird nicht auf Treu und Glauben genommen, sondern gegen `stack_required_collections()` geprüft, das die Menge aus dem `base_dir:` des `source`-Type-Specs ableitet. Eine separate Liste im Code gibt es nicht.
### Die Stelle, an der es sonst Prosa-Umschichtung geblieben wäre
`RELATIONSHIPS = "Beziehungen"` in `sections.py` war der Punkt, an dem die Konvention in Code übergelaufen war. Solange sie dort stand, konnte kein Template die Sprache umstellen. Das Modul las die drei Namen ab 3.0.0 aus den Instanz-Konventionen; der Alias-Mechanismus war der Migrationspfad. **In 4.0.0 (#40) ist `sections.py` ersatzlos entfallen** — Marker-Regionen tragen die Identität, der Überschriftentext ist reine Anzeige.
## Verworfen: `sources/` aus `kb/` herausziehen
Erwogen, weil `sources` sich strukturell von `entities` unterscheidet. Verworfen, gemessen:
- **Der Graph ist einwurzelig.** `kb_scan.iter_kb_pages(kb_dir)` macht ein `kb_dir.rglob("*.md")`; darauf sitzen `build_link_graph`, `inbound_links`, `find_duplicate_title_paths`, Orphan-Erkennung, `index rebuild`, `search`. Source-Seiten sind der dichteste Knotentyp darin — jedes `[^cite-id]` löst gegen eine auf. Ein Hoist macht jede Graph-Operation dauerhaft zweiwurzelig, um ein Verzeichnis umzubenennen.
- **`kb_collections` erzwingt „kein `COLLECTION.md` außerhalb `kb/`".** Ein Top-Level-`sources/` verlöre entweder seinen Contract oder diese Invariante bekäme eine Ausnahme.
- **Der Ort kann Eigentum ohnehin nicht kodieren**, sobald Collections offen sind (`kb_collections.py`: *„`mkdir kb/<name> && $EDITOR kb/<name>/COLLECTION.md` add a collection without a code change"*; `types/type-spec.md:45`: *„Adding a type must require no code change"*). Eine selbst angelegte Collection liegt im selben `kb/` wie die Defaults. Eigentum ist eine **deklarierte**, keine Orts-Eigenschaft — daher das Frontmatter oben.
Sichtbar halten, ohne es zu nehmen: `types/type-spec.md` kennt bereits `root: kb | repo`. Eine Seite außerhalb `kb/` ist schema-seitig vorgesehen; es fehlt der zweiwurzelige Graph-Walker. Wenn je eine Stage außerhalb `kb/` Seiten tragen soll, ist das die Stelle — kein Sonderfall für `sources`.
## Umsetzung
1. [x] `kb/CONTRACT.md` schneiden; `kb/CONVENTIONS.md` + `.template`; Eintrag in `dist_cmd.py`; `doctor`-Check analog zum Personalization-Sentinel.
2. [x] `COLLECTION.md`-Frontmatter (`profile:`, `required_by_stack:`) in `kb_collections.py` + `docs verify`; die vier Default-Contracts als Templates.
3. [x] `sections.py` und die vier Type-Specs von den hartkodierten deutschen Namen lösen.
4. [x] `instructions/kb-profiles.md` schreiben; `setup-instance.md` Schritt 5 auf „Profil wählen" umstellen; `private-instance.md`s Decision Point korrigieren; Version-Bump.
## Akzeptanzkriterien
- [x] Kein Satz in `kb/CONTRACT.md`, den eine Instanz ändern müsste
- [x] `kb/CONVENTIONS.md` ist nicht im Distributions-Dateiplan; nur das Template ist es
- [x] `doctor` FAILt auf fehlende `CONVENTIONS.md` und auf eine mit Template-Sentinel
- [x] `docs verify` prüft `profile:` und `required_by_stack:` auf jedem `COLLECTION.md`
- [x] Eine englischsprachige Instanz ändert **keine** Datei unter `tools/` oder `types/`
- [x] `setup-instance.md` Schritt 5 nennt eine Entscheidung, keine fünf Editierstellen
- [x] `private-instance.md`s Decision Point widerspricht dem nicht mehr
- [x] Version-Bump erfolgt nach `instructions/dev/version-parts.md`; der Verdacht auf Boundary-Crossing hat sich bestätigt und wurde als `3.0.0` mit Migrationsdokument gelöst
## Vorgeschichte
Entschieden in der Sitzung vom 2026-09-02, aus #30 heraus. Die Alternativen (`/sources`-Hoist, Marker-Blöcke im bestehenden Text, nur die Pfadmenge maschinenlesbar machen) sind oben mit Begründung verworfen.
---
## Nach Abschluss geht es weiter bei
**#28** — der kuratierte Fixture-Korpus. Er muss gegen die hier entschiedene Contract-Form autorisiert werden; vorher geschrieben, wird er zweimal geschrieben. Danach #30.
Changelog: Body auf den Abschlussstand gezogen — Status-Zeile vorangestellt, alle Umsetzungsschritte und Akzeptanzkriterien abgehakt, Tempus von Plan auf Befund umgestellt. Zwei sachliche Nachträge aus 4.0.0 eingearbeitet: required_by_stack: wird gegen stack_required_collections() geprüft statt auf Treu und Glauben genommen, und sections.py ist inzwischen ersatzlos entfallen statt nur von den deutschen Namen gelöst. Das letzte Akzeptanzkriterium präzisiert: der Boundary-Crossing-Verdacht hat sich bestätigt und wurde als 3.0.0 gelöst.
Abschluss
Umgesetzt und ausgeliefert als 3.0.0, Commit 502971d. Am 2026-09-03 gegen den laufenden Baum nachverifiziert.
Was implementiert wurde, entlang der vier Umsetzungspunkte:
Ergebnis
1
kb/CONVENTIONS.md existiert samt .template, ist nicht im Distributions-Dateiplan, doctor prüft sie (conventions: present, language de, sections Beziehungen, Fußnoten)
2
Alle vier COLLECTION.md tragen profile: und required_by_stack:; docs verify prüft beide, required_by_stack: gegen die aus dem source-Type-Spec abgeleitete Menge
3
instructions/german-terminology.md deklariert sich selbst als Bestandteil des german-Sprachprofils und nicht des Stacks; die vier Page-Type-Specs gehören seit 4.0.0 vollständig der Instanz
4
instructions/kb-profiles.md existiert; setup-instance.md Schritt 5 stellt eine Frage und wählt ein Profil; private-instance.md listet kb/CONTRACT.md, kb/CONVENTIONS.md.template und raw/CONTRACT.md namentlich als die drei Dateien, die der Merge aus MERGE_HEAD zurückholt, und die Kontrollzeile filtert genau diese drei heraus
Was bewusst nicht umgesetzt wurde: der /sources-Hoist aus dem Abschnitt Verworfen. Die Begründung steht im Body und hat sich seither bestätigt — 4.0.0 hat STACK_REQUIRED_TYPES eingeführt und STACK_REQUIRED_COLLECTIONS als separate Liste gestrichen, also ist Eigentum jetzt an genau einer Stelle deklariert statt an zwei. Der zweiwurzelige Graph-Walker bleibt der offene Vorbehalt, falls je eine Stage außerhalb kb/ Seiten tragen soll; ein eigenes Issue hat er nicht, weil es dafür heute keinen Anlass gibt.
Verifiziert:docs verify, instructions verify, doctor und lint --fail-on-error grün; 863 Tests; dist export erzeugt 191 Dateien mit kb/CONVENTIONS.md.template und je einem COLLECTION.md.template, und find_leaks verweigert die gefüllten Namen. Der volle setup-instance.md-Replay gegen einen frischen Export lief bei der Umsetzung durch — eine englische Instanz scaffoldet ## Relationships / ## See Also aus ihrer eigenen kb/CONVENTIONS.md und ändert dabei keine Datei unter tools/ oder types/.
Anschluss laut Body: #28 (kuratierter Fixture-Korpus), danach #30.
**Changelog:** Body auf den Abschlussstand gezogen — Status-Zeile vorangestellt, alle Umsetzungsschritte und Akzeptanzkriterien abgehakt, Tempus von Plan auf Befund umgestellt. Zwei sachliche Nachträge aus 4.0.0 eingearbeitet: `required_by_stack:` wird gegen `stack_required_collections()` geprüft statt auf Treu und Glauben genommen, und `sections.py` ist inzwischen ersatzlos entfallen statt nur von den deutschen Namen gelöst. Das letzte Akzeptanzkriterium präzisiert: der Boundary-Crossing-Verdacht hat sich bestätigt und wurde als `3.0.0` gelöst.
---
## Abschluss
Umgesetzt und ausgeliefert als **`3.0.0`**, Commit `502971d`. Am 2026-09-03 gegen den laufenden Baum nachverifiziert.
**Was implementiert wurde**, entlang der vier Umsetzungspunkte:
| | Ergebnis |
|---|---|
| 1 | `kb/CONVENTIONS.md` existiert samt `.template`, ist nicht im Distributions-Dateiplan, `doctor` prüft sie (`conventions: present, language de, sections Beziehungen, Fußnoten`) |
| 2 | Alle vier `COLLECTION.md` tragen `profile:` und `required_by_stack:`; `docs verify` prüft beide, `required_by_stack:` gegen die aus dem `source`-Type-Spec abgeleitete Menge |
| 3 | `instructions/german-terminology.md` deklariert sich selbst als Bestandteil des `german`-Sprachprofils und nicht des Stacks; die vier Page-Type-Specs gehören seit 4.0.0 vollständig der Instanz |
| 4 | `instructions/kb-profiles.md` existiert; `setup-instance.md` Schritt 5 stellt eine Frage und wählt ein Profil; `private-instance.md` listet `kb/CONTRACT.md`, `kb/CONVENTIONS.md.template` und `raw/CONTRACT.md` namentlich als die drei Dateien, die der Merge aus `MERGE_HEAD` zurückholt, und die Kontrollzeile filtert genau diese drei heraus |
**Was bewusst nicht umgesetzt wurde:** der `/sources`-Hoist aus dem Abschnitt *Verworfen*. Die Begründung steht im Body und hat sich seither bestätigt — 4.0.0 hat `STACK_REQUIRED_TYPES` eingeführt und `STACK_REQUIRED_COLLECTIONS` als separate Liste gestrichen, also ist Eigentum jetzt an genau einer Stelle deklariert statt an zwei. Der zweiwurzelige Graph-Walker bleibt der offene Vorbehalt, falls je eine Stage außerhalb `kb/` Seiten tragen soll; ein eigenes Issue hat er nicht, weil es dafür heute keinen Anlass gibt.
**Verifiziert:** `docs verify`, `instructions verify`, `doctor` und `lint --fail-on-error` grün; 863 Tests; `dist export` erzeugt 191 Dateien mit `kb/CONVENTIONS.md.template` und je einem `COLLECTION.md.template`, und `find_leaks` verweigert die gefüllten Namen. Der volle `setup-instance.md`-Replay gegen einen frischen Export lief bei der Umsetzung durch — eine englische Instanz scaffoldet `## Relationships` / `## See Also` aus ihrer eigenen `kb/CONVENTIONS.md` und ändert dabei keine Datei unter `tools/` oder `types/`.
Anschluss laut Body: **#28** (kuratierter Fixture-Korpus), 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
kb/CONTRACT.mdist eine Datei mit zwei Autoritäten. Ein Teil ist code-erzwungen und darf nie abweichen; ein Teil ist Konvention, die jede Instanz für sich entscheidet — und wird trotzdem als bindender Contract ausgeliefert.docs verify), Generated-Files-Tabelle,provenance:-Enum, Cite-/Footnote-Mechanik,confidence_basevs. abgeleitetesconfidence, Orphan-Check, Blockquote-Caphängt ab von,verwendet, …), Ton-Beispiele und Buzzword-Liste (deutsch), Confidence-Rubrik (0.5 / +0.2 pro Quelle / …), ADR-PräfixDie Sprachentscheidung ist der Extremfall: sie steckt nicht in einer Datei, sondern in vier Schichten.
setup-instance.mdSchritt 5 wusste das und löste es als Handarbeit an fünf Stellen über drei Schichten.Warum das mehr war als Unordnung
Zwei Dokumente widersprachen sich für jede nicht-deutsche Instanz. Wer bei Schritt 5 „Englisch" antwortete, hatte lokal
kb/CONTRACT.md, vier Type-Specs undsections.pygeändert.private-instance.mds Decision Point sagt für so einen Merge-Konflikt: „Conflict intools/,types/orinstructions/? You changed the stack locally — take the upstream side and re-file the change as an issue there." Die Upstream-Seite nehmen hieß für diese Instanz: KB-Sprache zurück auf Deutsch.Und die Merge-Prozedur aus 2.2.1 hatte dadurch ein zweites Leck, in die andere Richtung als das in #30 beschriebene.
git checkout HEAD -- kb rawholt alles unter beiden Stages auf den Vor-Merge-Stand — auch diese sechs Maschinerie-Dateien:Änderte der Upstream einen davon, warf die Prozedur das Update still weg. Die Kontrollzeile
git diff --name-only $BEFORE HEAD -- kb rawmeldete dann leer, also „nachweislich funktioniert", während sie gerade eine Contract-Änderung gefressen hatte. Die Kontrolle, die der wichtigste Teil sein soll, bestätigte hier den Fehler.dist_cmd.pykannte die Unterscheidung bereits (CONTRACT_ONLY_STAGESplus jedeskb/*/COLLECTION.mdwerden explizit kopiert). Die Prosa-Prozedur hatte sie nicht.Vorbild: Commonplace
Gemessen an
commonplace/(vendored, sieheinstructions/dev/commonplace-kb.md), drei zusammengehörige Mechanismen:COLLECTION.mdstays authoritative for the collection's actual contract; a profile entry here is a proven starting point, not a binding source." Das Ausgelieferte ist unverbindliche Referenz, das Instanz-eigene bindet. Bei uns war es andersherum..commonplace): bei Drift wird nicht überschrieben, sondern verweigert.Dazu der Begriff, der die Diagnose trägt: system-definition artifact (bindet) vs. knowledge artifact (informiert).
Disanalogie, die die Kopie begrenzt: ADR-021s Namespace (
kb/commonplace/) löst „ausgelieferter Inhalt kollidiert mit Nutzerinhalt" — 195 Methodik-Notizen imkb/notes/des Nutzers. Chemenu liefert null Seiten aus. Unser Problem war nicht ausgelieferter Inhalt, sondern ausgelieferter Contract. Der Namespace war deshalb die falsche Hälfte; die richtige sind Punkt 1 und 2.Entscheidung
Der Schnitt lief bis dahin nach Ort — alles über
kb/steht inkb/CONTRACT.md. Er läuft seit 3.0.0 danach, wer den Satz ändern darf.kb/CONTRACT.mdkb/CONVENTIONS.md.templatekb/*/COLLECTION.md.templateje Default-Collection, adoptiert ein Profil per Referenzinstructions/kb-profiles.mdkb/CONVENTIONS.mdträgt: KB-Sprache samt Abschnittsnamen, Beziehungslabel-Vokabular, Ton-Beispiele und Buzzword-Liste, Confidence-Rubrik, ADR-Präfix. Die Distribution liefert nurkb/CONVENTIONS.md.template— exakt derUSER.md/SOUL.md-Split, denROOT_FILESunddoctors Sentinel-Check schon konnten. Kein neuer Mechanismus, ein vorhandener ein Verzeichnis tiefer.COLLECTION.mdbekam FrontmatterBis dahin wurde eine Collection rein an der Dateipräsenz erkannt (
kb_collections.py). Die Deklaration brauchte einen Träger, sonst hätten wir den Ortsschnitt durch einen Prosaschnitt ersetzt:required_by_stack: trueheißt: Autorenkonventionen darf die Instanz umschreiben, löschen oder umbenennen nicht. Das trifftsources(sources coverage, Cite-Auflösung,provenance.md), nichtentities. Geprüft vondocs verify.Nachtrag aus 4.0.0:
required_by_stack:wird nicht auf Treu und Glauben genommen, sondern gegenstack_required_collections()geprüft, das die Menge aus dembase_dir:dessource-Type-Specs ableitet. Eine separate Liste im Code gibt es nicht.Die Stelle, an der es sonst Prosa-Umschichtung geblieben wäre
RELATIONSHIPS = "Beziehungen"insections.pywar der Punkt, an dem die Konvention in Code übergelaufen war. Solange sie dort stand, konnte kein Template die Sprache umstellen. Das Modul las die drei Namen ab 3.0.0 aus den Instanz-Konventionen; der Alias-Mechanismus war der Migrationspfad. In 4.0.0 (#40) istsections.pyersatzlos entfallen — Marker-Regionen tragen die Identität, der Überschriftentext ist reine Anzeige.Verworfen:
sources/auskb/herausziehenErwogen, weil
sourcessich strukturell vonentitiesunterscheidet. Verworfen, gemessen:kb_scan.iter_kb_pages(kb_dir)macht einkb_dir.rglob("*.md"); darauf sitzenbuild_link_graph,inbound_links,find_duplicate_title_paths, Orphan-Erkennung,index rebuild,search. Source-Seiten sind der dichteste Knotentyp darin — jedes[^cite-id]löst gegen eine auf. Ein Hoist macht jede Graph-Operation dauerhaft zweiwurzelig, um ein Verzeichnis umzubenennen.kb_collectionserzwingt „keinCOLLECTION.mdaußerhalbkb/". Ein Top-Level-sources/verlöre entweder seinen Contract oder diese Invariante bekäme eine Ausnahme.kb_collections.py: „mkdir kb/<name> && $EDITOR kb/<name>/COLLECTION.mdadd a collection without a code change";types/type-spec.md:45: „Adding a type must require no code change"). Eine selbst angelegte Collection liegt im selbenkb/wie die Defaults. Eigentum ist eine deklarierte, keine Orts-Eigenschaft — daher das Frontmatter oben.Sichtbar halten, ohne es zu nehmen:
types/type-spec.mdkennt bereitsroot: kb | repo. Eine Seite außerhalbkb/ist schema-seitig vorgesehen; es fehlt der zweiwurzelige Graph-Walker. Wenn je eine Stage außerhalbkb/Seiten tragen soll, ist das die Stelle — kein Sonderfall fürsources.Umsetzung
kb/CONTRACT.mdschneiden;kb/CONVENTIONS.md+.template; Eintrag indist_cmd.py;doctor-Check analog zum Personalization-Sentinel.COLLECTION.md-Frontmatter (profile:,required_by_stack:) inkb_collections.py+docs verify; die vier Default-Contracts als Templates.sections.pyund die vier Type-Specs von den hartkodierten deutschen Namen lösen.instructions/kb-profiles.mdschreiben;setup-instance.mdSchritt 5 auf „Profil wählen" umstellen;private-instance.mds Decision Point korrigieren; Version-Bump.Akzeptanzkriterien
kb/CONTRACT.md, den eine Instanz ändern müsstekb/CONVENTIONS.mdist nicht im Distributions-Dateiplan; nur das Template ist esdoctorFAILt auf fehlendeCONVENTIONS.mdund auf eine mit Template-Sentineldocs verifyprüftprofile:undrequired_by_stack:auf jedemCOLLECTION.mdtools/odertypes/setup-instance.mdSchritt 5 nennt eine Entscheidung, keine fünf Editierstellenprivate-instance.mds Decision Point widerspricht dem nicht mehrinstructions/dev/version-parts.md; der Verdacht auf Boundary-Crossing hat sich bestätigt und wurde als3.0.0mit Migrationsdokument gelöstVorgeschichte
Entschieden in der Sitzung vom 2026-09-02, aus #30 heraus. Die Alternativen (
/sources-Hoist, Marker-Blöcke im bestehenden Text, nur die Pfadmenge maschinenlesbar machen) sind oben mit Begründung verworfen.Nach Abschluss geht es weiter bei
#28 — der kuratierte Fixture-Korpus. Er muss gegen die hier entschiedene Contract-Form autorisiert werden; vorher geschrieben, wird er zweimal geschrieben. Danach #30.
Changelog: Body auf den Abschlussstand gezogen — Status-Zeile vorangestellt, alle Umsetzungsschritte und Akzeptanzkriterien abgehakt, Tempus von Plan auf Befund umgestellt. Zwei sachliche Nachträge aus 4.0.0 eingearbeitet:
required_by_stack:wird gegenstack_required_collections()geprüft statt auf Treu und Glauben genommen, undsections.pyist inzwischen ersatzlos entfallen statt nur von den deutschen Namen gelöst. Das letzte Akzeptanzkriterium präzisiert: der Boundary-Crossing-Verdacht hat sich bestätigt und wurde als3.0.0gelöst.Abschluss
Umgesetzt und ausgeliefert als
3.0.0, Commit502971d. Am 2026-09-03 gegen den laufenden Baum nachverifiziert.Was implementiert wurde, entlang der vier Umsetzungspunkte:
kb/CONVENTIONS.mdexistiert samt.template, ist nicht im Distributions-Dateiplan,doctorprüft sie (conventions: present, language de, sections Beziehungen, Fußnoten)COLLECTION.mdtragenprofile:undrequired_by_stack:;docs verifyprüft beide,required_by_stack:gegen die aus demsource-Type-Spec abgeleitete Mengeinstructions/german-terminology.mddeklariert sich selbst als Bestandteil desgerman-Sprachprofils und nicht des Stacks; die vier Page-Type-Specs gehören seit 4.0.0 vollständig der Instanzinstructions/kb-profiles.mdexistiert;setup-instance.mdSchritt 5 stellt eine Frage und wählt ein Profil;private-instance.mdlistetkb/CONTRACT.md,kb/CONVENTIONS.md.templateundraw/CONTRACT.mdnamentlich als die drei Dateien, die der Merge ausMERGE_HEADzurückholt, und die Kontrollzeile filtert genau diese drei herausWas bewusst nicht umgesetzt wurde: der
/sources-Hoist aus dem Abschnitt Verworfen. Die Begründung steht im Body und hat sich seither bestätigt — 4.0.0 hatSTACK_REQUIRED_TYPESeingeführt undSTACK_REQUIRED_COLLECTIONSals separate Liste gestrichen, also ist Eigentum jetzt an genau einer Stelle deklariert statt an zwei. Der zweiwurzelige Graph-Walker bleibt der offene Vorbehalt, falls je eine Stage außerhalbkb/Seiten tragen soll; ein eigenes Issue hat er nicht, weil es dafür heute keinen Anlass gibt.Verifiziert:
docs verify,instructions verify,doctorundlint --fail-on-errorgrün; 863 Tests;dist exporterzeugt 191 Dateien mitkb/CONVENTIONS.md.templateund je einemCOLLECTION.md.template, undfind_leaksverweigert die gefüllten Namen. Der vollesetup-instance.md-Replay gegen einen frischen Export lief bei der Umsetzung durch — eine englische Instanz scaffoldet## Relationships/## See Alsoaus ihrer eigenenkb/CONVENTIONS.mdund ändert dabei keine Datei untertools/odertypes/.Anschluss laut Body: #28 (kuratierter Fixture-Korpus), danach #30.