feat: contradict an incomplete hand-reconciliation, not just a forgotten one

Slice 4.2. The receipt duty from ADR-0024 was built yesterday on the
premise that "the error is forgetting, not doing it wrong". The same
upgrade falsified that on the same day: the receipt for schema.yaml was
written, it is truthful, it names correctly what was carried over - and two
fields were lost anyway. Nobody forgot anything.

pruefe_upstream_drift.py now holds our schema.yaml structurally against the
baseline: every type, field, enum value and required entry the baseline
carries must exist here. Adding is allowed, losing is not. Checked
counterfactually against the real loss, which it reports.

For the two other extended files there is no equivalent - they are Python,
and no structural comparison exists. That half stays open and is written
down rather than glossed over.

Building it taught the check two things about itself. It broke the existing
selftest because that fixture wrote schema.yaml as prose - the same
unrealistic fixture FB-12 already records as having made an assertion
worthless for this very script. And my counter-control reported "nothing"
for the removed crash guard, because a crash returns the same exit code as
a failure; only checking for a traceback showed the script never reaches
its summary without it. Both times the control was blunt, not the check.

Sixteen assertions, six deliberate breaks, none uncovered.

The occasion is recorded for the next harvest as two stolpersteine pages -
the first use of the mechanism ADR-0009 decided: a receipt certifies
attention rather than completeness, and a file cannot be authoritative and
immutable at once.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F2Q4Ri8NGwyTZzScvKnWFM
This commit is contained in:
Thore Cimbal
2026-08-21 12:00:00 +00:00
co-authored by Claude Opus 5
parent 0a9543703d
commit fe5286c420
7 changed files with 309 additions and 5 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ als Wiki-Seiten unter [docs/wiki/stolpersteine/](docs/wiki/index.md)
— eine Seite je Muster, mit Stand und, wo zutreffend, der Version,
die es abdeckt (ADR-0009 des Rahmenwerks).
**Die geclusterte Übersicht steht in [STATUS.md](STATUS.md)**, Abschnitt *Fehlerklassen*: 0 offen, 0 teilweise, 1 geerntet, 0 nicht abgedeckt.
**Die geclusterte Übersicht steht in [STATUS.md](STATUS.md)**, Abschnitt *Fehlerklassen*: 2 offen, 0 teilweise, 1 geerntet, 0 nicht abgedeckt.
Diese Datei hält keinen eigenen Bestand mehr. Sie bleibt als
Wegweiser bestehen, weil angenommene Entscheidungen auf sie
+6 -1
View File
@@ -131,7 +131,12 @@ Bedeutung der Meilensteine: siehe [roadmap.md](roadmap.md).
- [AAR — Game-Host-Anbindung: Deploy über zwei Übergaben, Ursache war nie die Firewall](docs/aar/2026-08-20-game-host-anbindung.md)
- [AAR — rohana lief voll, der ThreadNet-Web-Spiegel riss](docs/aar/2026-08-20-rohana-platte-voll-spiegel-gerissen.md)
## Fehlerklassen (3)
## Fehlerklassen (5)
### offen (2)
- [Eine Datei kann nicht maßgeblich und zugleich unveränderlich sein](docs/wiki/stolpersteine/massgeblich-aber-unveraenderlich.md)
- [Eine Quittung bezeugt Aufmerksamkeit, nicht Vollständigkeit](docs/wiki/stolpersteine/quittung-bezeugt-aufmerksamkeit.md)
### geerntet (1)
@@ -16,7 +16,8 @@ related:
| 1 | acbef08 | sorb | DONE | Zuschnitt einmal komplett neu gefasst, weil die Messung ergab, dass Etappe 1 schon lief. Freigabe mit Auflage: erst pruefen, ob v0.3.1 laengst adaptiert ist. |
| 2 | afaa74a | sorb | DONE | Auflage erfuellt: Regeln vollstaendig uebernommen, inhaltlicher Rest offen. Option A gewaehlt. |
| 3 | b179246 | sorb | DONE | Ausfuehrung von A angepasst (Wegweiser statt Loeschung); Enum auf Einwand um `declined` erweitert. |
| 4.1 | | | DONE | Tracer Bullet: Schema, Generator, eine Seite. Acht Kontrollen gefahren, davon sieben absichtliche Brueche. |
| 4.1 | 0a95437 | sorb | DONE | Tracer Bullet: Schema, Generator, eine Seite. Acht Kontrollen gefahren, davon sieben absichtliche Brueche. |
| 4.2 | | sorb | DONE | Schema-Deckung als Widerspruch zum Handabgleich; zwei Befundseiten fuer die naechste Ernte. 16 Zusicherungen, sechs Brueche. |
## Ladder
@@ -31,9 +32,21 @@ related:
| `collect()` in `gen_status.py`, bevor ein Leser für die Wiki-Seiten geschrieben wird | liest jedes Artefakt nach Verzeichnis und Typ, inklusive Frontmatter | reused: nur ein Filter auf `area` kommt dazu, kein zweiter Leser | |
| Erweiterungsblock in `schema.yaml` und ADR-0024, ob ein vierter Status-Wert überhaupt zulässig ist | zwei Enum-Erweiterungen sind dort bereits erklärt und gedeckt | reused: geübte Praxis statt Ausnahmeantrag — `declined` kommt in denselben Block | |
| `apply_rules()` in `validate.py`, für die Beleg-Pflicht bei entschiedenen Ständen | vier Regeln nach demselben Muster, u. a. `waiting_requires_reason` | reused: eine fünfte Regel im selben Schalter, kein eigener Prüfer | |
| `pruefe_upstream_drift.py`, bevor ein eigener Schema-Vergleicher entsteht | prüft bereits Baselines, Zuordnung und Abgleichs-Quittung — nur nicht den Inhalt der erweiterten Dateien | reused: fünfte Prüffunktion im selben Skript samt dessen Selbsttest, kein neues Werkzeug | |
| `FRAMEWORK-BEFUNDE.md`, wohin die zwei neuen Befunde gehören | seit heute erzeugter Wegweiser ohne Bestand; Muster leben als Wiki-Seiten (ADR-0009) | built: zwei `stolpersteine`-Seiten — der erste Einsatz des neuen Mechanismus | |
## Notes
**Die neue Prüfung hat beim Bauen zweimal etwas über sich selbst gelernt.**
Erst brach sie den bestehenden Selbsttest, weil dessen Fixture `schema.yaml`
als Prosa schrieb — genau die unrealistische Fixture, die FB-12 für dieses
Skript schon einmal als Ursache einer wertlosen Zusicherung festhält. Dann
meldete meine Gegenprobe für den entfernten Absturzschutz „nichts", weil ein
Absturz denselben Rückgabewert liefert wie ein Fehlschlag; erst die Prüfung
auf einen Traceback zeigte, dass das Skript ohne den Schutz gar nicht bis zu
seiner Zusammenfassung kommt. Beide Male war nicht die Prüfung stumpf,
sondern ihre Kontrolle.
**Eine unsichere Entscheidung wurde auf Einwand verworfen.** Gate 3 wollte
FB-07 und FB-08 mit `open` führen, obwohl das Rahmenwerk sie abgelehnt hat —
mit dem Argument, der Enum habe keinen passenden Wert. sorb hat gefragt,
@@ -0,0 +1,76 @@
---
type: wiki-page
area: stolpersteine
status: open
related:
- "docs/adr/0024-drei-framework-dateien-erklaert-erweitert.md"
- "docs/wiki/stolpersteine/quittung-bezeugt-aufmerksamkeit.md"
---
# Eine Datei kann nicht maßgeblich und zugleich unveränderlich sein
Zwei Regeln, jede für sich richtig, treffen sich auf derselben Datei und
schließen einander aus. Der Widerspruch fällt nicht beim Beschließen auf,
sondern beim ersten Versuch, die Datei zu benutzen.
## Der belegte Fall
[ADR-0024](../../adr/0024-drei-framework-dateien-erklaert-erweitert.md)
erklärt drei Framework-Dateien für projekterweitert und legt fest:
> *„Maßgeblich ist die Tabelle in `HERKUNFT.md` der jeweils gültigen
> Baseline. Sie und die Paarliste in `pruefe_upstream_drift.py` müssen sich
> decken."*
`HERKUNFT.md` liegt unter `docs/sources/` — und `docs/sources/` ist
unveränderlich. Gemessen, nicht vermutet: Ein Testcommit auf die Datei
liefert
```
pruefe_sperrliste: 1 Verstoss/Verstoesse im Bereich HEAD~1..HEAD
```
Damit ist die Tabelle, die deckungsgleich gehalten werden **muss**, die
einzige, die niemand nachziehen **darf**. Aufgefallen ist es am
2026-08-21, als eine Schema-Erweiterung dort einzutragen gewesen wäre; sie
steht jetzt ersatzweise im Erweiterungsblock von `schema.yaml`.
## Warum es beim Beschließen unsichtbar war
Beide Regeln stammen von verschiedenen Tagen und aus verschiedenen
Anlässen. Die Unveränderlichkeit von `docs/sources/` ist alt und gut
begründet: Originalquellen werden zitiert, nicht redigiert. Die
Maßgeblichkeit von `HERKUNFT.md` ist einen Tag alt und ebenfalls gut
begründet: Der Handabgleich braucht einen benannten Ort.
Der Fehler liegt in keiner der beiden Regeln, sondern **dazwischen**: Beim
Beschließen der zweiten wurde nicht gefragt, welchen Regeln der gewählte
Ort bereits unterliegt. Eine Datei erbt die Regeln ihres Verzeichnisses,
und ein Verzeichnis für unveränderliche Originale ist der falsche Platz für
ein gepflegtes Verzeichnis.
## Woran man es erkennt
- Eine neue Pflicht benennt einen Ort, ohne dessen bestehende Regeln zu
nennen.
- Zwei Regeln, die je für sich zitiert werden und nie nebeneinander.
- Ein Widerspruch, der erst bei der **zweiten** Benutzung sichtbar wird —
beim Anlegen war die Datei ein erlaubter Zugang, erst beim Fortschreiben
wird sie eine verbotene Änderung.
## Was dagegen hilft
Beim Festlegen eines Ortes prüfen, welche Regeln dort schon gelten — eine
Frage, keine Prüfung. Und die Lösung selbst ist eine Entscheidung, keine
Reparatur: Entweder zieht die maßgebliche Tabelle aus `docs/sources/`
heraus, oder die Sperre bekommt eine benannte, begründete Ausnahme. Beides
löst eine angenommene ADR ab und gehört ins Refinement, nicht in einen
Nebensatz.
## Für die nächste Ernte
Verallgemeinert: Ein Rahmenwerk, das ein Verzeichnis für unveränderliche
Quellen vorsieht **und** Übernehmer auffordert, dort ihre Baseline samt
Begleitnotizen abzulegen, baut diesen Konflikt für jeden Übernehmer ein.
Die Frage nach oben ist nicht, wie wir ihn hier auflösen, sondern wohin
gepflegte Metadaten zu einer unveränderlichen Quelle gehören.
@@ -0,0 +1,82 @@
---
type: wiki-page
area: stolpersteine
status: open
related:
- "docs/adr/0024-drei-framework-dateien-erklaert-erweitert.md"
- "docs/wiki/stolpersteine/meldet-erfolg-ist-aber-blind.md"
---
# Eine Quittung bezeugt Aufmerksamkeit, nicht Vollständigkeit
Wo eine Pflicht nicht maschinell prüfbar ist, liegt der Griff nahe, statt
der Sache die **Erinnerung daran** zu prüfen: Wer bestätigt, dass er es
getan hat, hat offenbar daran gedacht. Das ist billig zu bauen, fühlt sich
wie ein Widerspruch an — und deckt nur die Hälfte ab.
## Der belegte Fall
Am 2026-08-21 bekam der Abgleich der erklärt-erweiterten Framework-Dateien
(ADR-0024) einen Widerspruch: Hat upstream zwischen zwei Baselines eine
solche Datei angefasst, verlangt `pruefe_upstream_drift.py` eine Zeile in
`ABGLEICH.tsv`. Die Begründung stand ausdrücklich dabei:
> *„Sie kann nicht entscheiden, ob wir richtig nachgezogen haben — aber sie
> kann sagen, dass es zu tun war. Das genügt: Der Fehler ist Vergessen,
> nicht Falschmachen."*
**Dieselbe Aufgabe hat den Satz am selben Tag widerlegt.** Die Quittung für
`schema.yaml` wurde geschrieben, sie ist wahrheitsgemäß, und sie nennt
korrekt, was übernommen wurde:
```
schema.yaml neckbeard-v0.1.1 vendored, project_section_marker, judged,
Typen ledger und verdict uebernommen
```
Verloren gingen trotzdem **zwei Felder**`wiki-page.status` und
`wiki-page.harvested_in`, also genau der Mechanismus, den ADR-0009 des
Rahmenwerks entscheidet. Aufgefallen ist es erst einen Tag später, als
jemand ihn benutzen wollte und feststellte, dass das Feld nicht existiert.
Niemand hat etwas vergessen. Es wurde daran gedacht, es wurde gearbeitet,
es wurde quittiert — und es war unvollständig. Die Quittung konnte das
nicht sehen, weil sie nach dem *Ob* fragt und nicht nach dem *Wie weit*.
## Woran man es erkennt
- Eine Prüfung, die eine **Bestätigung** verlangt statt eines **Vergleichs**.
- Eine Begründung, die eine Fehlerart ausschließt („der Fehler ist X, nicht
Y") — ohne dass jemand Y gemessen hat.
- Ein Handgriff, dessen Ergebnis niemand gegen die Quelle hält.
## Was dagegen hilft
**Vergleichen, wo Vergleichen geht.** Seit dem 2026-08-21 hält
`pruefe_upstream_drift.py` unsere `schema.yaml` strukturell gegen die
Baseline: Jeder Typ, jedes Feld, jeder Enum-Wert und jede Pflichtangabe der
Baseline muss bei uns vorhanden sein. Hinzufügen ist erlaubt — wir
erweitern erklärt —, **verlieren nicht**. Kontrafaktisch gegen den echten
Verlust geprüft: Die Prüfung meldet ihn.
⚠️ **Für die beiden anderen erweiterten Dateien gibt es das nicht.**
`validate.py` und `gen_status.py` sind Python; ein struktureller Vergleich
wie bei YAML existiert nicht. Dort trägt weiterhin nur die Quittung — und
der Fall oben zeigt, wie weit sie trägt. Das ist die offene Hälfte.
**Die Ausschluss-Behauptung ist der eigentliche Fehler.** Nicht die
Quittung war falsch, sondern der Satz „der Fehler ist Vergessen, nicht
Falschmachen". Er hat eine Fehlerart per Annahme aus dem Entwurf entfernt,
ohne sie zu messen — dieselbe Bewegung wie bei
[„Meldet Erfolg, ist aber blind"](meldet-erfolg-ist-aber-blind.md), nur
eine Ebene höher: dort behauptet ein Werkzeug ein Ergebnis, hier behauptet
eine Begründung eine Fehlerverteilung.
## Für die nächste Ernte
Das Muster ist nicht projektspezifisch: Jedes Rahmenwerk, das Dateien zum
Erweitern freigibt, bekommt dasselbe Problem. Wert für die Übergabe ist
weniger die Quittung als die Frage, die ihr fehlt — **woran misst sich,
dass ein Handabgleich vollständig war?** Für strukturierte Formate ist die
Antwort billig. Für Code ist sie offen, und das gehört gesagt statt
überspielt.
+130 -2
View File
@@ -51,6 +51,11 @@ import sys
import tempfile
from pathlib import Path
try:
import yaml
except ImportError: # pragma: no cover
sys.exit("pruefe_upstream_drift.py braucht PyYAML: pip install pyyaml")
UPSTREAM = "docs/sources/upstream"
BASELINE = f"{UPSTREAM}/neckbeard-v0.3.1"
MARKE = "<!-- projektabschnitt -->"
@@ -88,6 +93,23 @@ NICHT_UEBERNOMMEN = (
# das über den Präfix-Vergleich läuft.
SONDERFALL = (HERKUNFT, ABGLEICH, "AGENTS.md")
# Fixture fuer den Selbsttest: echtes, minimales YAML in der Form, die die
# Deckungspruefung erwartet.
SCHEMA_FIXTURE = """link_fields: [related, judged]
types:
wiki-page:
required: [type, area]
fields:
status: { enum: [open, harvested] }
harvested_in: { kind: str }
"""
# Unsere Fassung: alles aus der Baseline plus eigene Zusaetze - ein vierter
# Enum-Wert und ein eigener Typ. Beides darf NICHT gemeldet werden.
SCHEMA_FIXTURE_UNSER = SCHEMA_FIXTURE.replace(
"{ enum: [open, harvested] }", "{ enum: [open, harvested, declined] }"
) + " eigener_typ:\n required: [type]\n"
def baseline_dateien(base: Path) -> list[str]:
return sorted(str(p.relative_to(base)) for p in base.rglob("*") if p.is_file())
@@ -193,6 +215,68 @@ def pruefe_abgleich(root: Path, fehler: list[str]) -> None:
f"sie nicht. Nachziehen und dort eintragen.")
def pruefe_schema_deckung(root: Path, base: Path, fehler: list[str]) -> None:
"""Deckt unsere schema.yaml noch alles ab, was die Baseline fuehrt?
⚠️ Anlass: Beim Upgrade auf v0.3.1 wurde der Handabgleich quittiert —
wahrheitsgemaess — und verlor trotzdem zwei Felder (wiki-page.status,
wiki-page.harvested_in). Die Quittungspflicht aus ADR-0024 fragt, OB
nachgezogen wurde, nicht OB VOLLSTAENDIG. Sie wurde in der Annahme
gebaut, der Fehler sei Vergessen und nicht Falschmachen; dieselbe
Aufgabe hat das an ihrem ersten Tag widerlegt.
Diese Pruefung schliesst die Luecke nur fuer schema.yaml, weil YAML
strukturell vergleichbar ist. Fuer die beiden Python-Dateien gibt es
keine Entsprechung — das bleibt offen und ist als Muster notiert.
Erlaubt ist Hinzufuegen (wir erweitern erklaert, ADR-0024), verboten
ist Verlieren.
"""
eigen_pfad, basis_pfad = root / "schema.yaml", base / "schema.yaml"
if not (eigen_pfad.is_file() and basis_pfad.is_file()):
fehler.append("schema.yaml fehlt in Arbeitskopie oder Baseline")
return
try:
eigen = yaml.safe_load(eigen_pfad.read_text(encoding="utf-8")) or {}
basis = yaml.safe_load(basis_pfad.read_text(encoding="utf-8")) or {}
except yaml.YAMLError as ex:
fehler.append(f"schema.yaml nicht lesbar: {str(ex)[:80]}")
return
# Ein Schema, das keine Abbildung ist, ist ein Fehler - kein Absturz und
# erst recht kein stilles Bestanden.
if not isinstance(eigen, dict) or not isinstance(basis, dict):
fehler.append("schema.yaml ist keine YAML-Abbildung "
"(Arbeitskopie oder Baseline)")
return
for schluessel in ("link_fields", "vendored"):
fehlt = set(basis.get(schluessel) or []) - set(eigen.get(schluessel) or [])
if fehlt:
fehler.append(f"schema.yaml: {schluessel} verliert {sorted(fehlt)} "
f"gegenueber {base.name}")
for typ, spec in (basis.get("types") or {}).items():
eigener = (eigen.get("types") or {}).get(typ)
if eigener is None:
fehler.append(f"schema.yaml: Typ '{typ}' aus {base.name} fehlt")
continue
for teil in ("fields", "required", "rules"):
hier = eigener.get(teil) or ([] if teil != "fields" else {})
dort = spec.get(teil) or ([] if teil != "fields" else {})
fehlt = set(dort) - set(hier)
if fehlt:
fehler.append(f"schema.yaml: {typ}.{teil} verliert "
f"{sorted(fehlt)} gegenueber {base.name}")
for feld, regel in (spec.get("fields") or {}).items():
werte = (regel or {}).get("enum")
unser = ((eigener.get("fields") or {}).get(feld) or {}).get("enum")
if werte and unser is not None:
fehlt = set(werte) - set(unser)
if fehlt:
fehler.append(f"schema.yaml: {typ}.{feld} Enum verliert "
f"{sorted(fehlt)} gegenueber {base.name}")
def pruefe(root: Path) -> list[str]:
base = root / BASELINE
fehler: list[str] = []
@@ -200,6 +284,7 @@ def pruefe(root: Path) -> list[str]:
pruefe_agents(root, base, fehler)
pruefe_vollstaendigkeit(base, fehler)
pruefe_abgleich(root, fehler)
pruefe_schema_deckung(root, base, fehler)
return fehler
@@ -241,6 +326,11 @@ def selbsttest() -> int:
for datei in ERWEITERT + NICHT_UEBERNOMMEN:
(b / datei).parent.mkdir(parents=True, exist_ok=True)
(b / datei).write_text("upstream-stand\n", encoding="utf-8")
# ⚠️ schema.yaml MUSS echtes YAML sein. Eine Prosa-Fixture laesst
# die Deckungspruefung abstuerzen statt pruefen - dieselbe Klasse,
# die bei diesem Skript schon einmal eine Zusicherung wertlos
# gemacht hat (FB-12, Punkt 3).
(b / "schema.yaml").write_text(SCHEMA_FIXTURE, encoding="utf-8")
(root / "AGENTS.md").write_text(f"upstream\n{MARKE}\neigenes\n",
encoding="utf-8")
for arbeit, _ in PAARE:
@@ -249,6 +339,8 @@ def selbsttest() -> int:
for datei in ERWEITERT:
(root / datei).parent.mkdir(parents=True, exist_ok=True)
(root / datei).write_text("unser erweiterter stand\n", encoding="utf-8")
(root / "schema.yaml").write_text(SCHEMA_FIXTURE_UNSER,
encoding="utf-8")
pruefe_dass("ein sauberer Stand meldet nichts", pruefe(root) == [])
@@ -268,7 +360,8 @@ def selbsttest() -> int:
any(f.startswith("UNZUGEORDNET:") for f in pruefe(root)))
(neu / "scripts" / "fremd.py").unlink()
(neu / ERWEITERT[0]).write_text("upstream hat angefasst\n", encoding="utf-8")
(neu / ERWEITERT[0]).write_text(SCHEMA_FIXTURE + "# oben angefasst\n",
encoding="utf-8")
pruefe_dass("ohne Quittungsdatei ist eine oben geaenderte erweiterte "
"Datei ein Fehler, kein Skip",
any(ABGLEICH in f and "fehlt" in f for f in pruefe(root)))
@@ -292,11 +385,46 @@ def selbsttest() -> int:
pruefe_dass("dieselbe Aenderung ist still, sobald ABGLEICH.tsv quittiert",
not any(f.startswith("ABGLEICH:") for f in pruefe(root)))
(neu / ERWEITERT[0]).write_text("upstream-stand\n", encoding="utf-8")
(neu / ERWEITERT[0]).write_text(SCHEMA_FIXTURE, encoding="utf-8")
(neu / ABGLEICH).unlink()
pruefe_dass("ohne Aenderung oben wird ueberhaupt keine Quittung verlangt",
not any(ABGLEICH in f for f in pruefe(root)))
shutil.rmtree(alt)
# --- Deckung der erklaert-erweiterten schema.yaml -------------------
# Anlass: Der Handabgleich beim v0.3.1-Upgrade wurde quittiert und
# verlor trotzdem zwei Felder. Die Quittungspflicht fragt OB, nicht
# OB VOLLSTAENDIG.
eigen = root / "schema.yaml"
eigen.write_text(SCHEMA_FIXTURE_UNSER.replace(
" harvested_in: { kind: str }\n", ""), encoding="utf-8")
pruefe_dass("ein verlorenes Feld der Baseline wird gemeldet",
any("wiki-page.fields verliert" in f for f in pruefe(root)))
eigen.write_text(SCHEMA_FIXTURE_UNSER.replace(
"[open, harvested, declined]", "[open, declined]"), encoding="utf-8")
pruefe_dass("ein verlorener Enum-Wert wird gemeldet",
any("Enum verliert" in f for f in pruefe(root)))
eigen.write_text(SCHEMA_FIXTURE_UNSER.replace(
" wiki-page:", " andere-seite:"), encoding="utf-8")
pruefe_dass("ein verlorener Typ wird gemeldet",
any("Typ 'wiki-page'" in f for f in pruefe(root)))
eigen.write_text(SCHEMA_FIXTURE_UNSER.replace(
"link_fields: [related, judged]", "link_fields: [related]"),
encoding="utf-8")
pruefe_dass("ein verlorenes link_fields wird gemeldet",
any("link_fields verliert" in f for f in pruefe(root)))
eigen.write_text("nur prosa, kein schema\n", encoding="utf-8")
befunde = pruefe(root)
pruefe_dass("ein Schema ohne Abbildung ist ein Fehler, kein Absturz",
any("keine YAML-Abbildung" in f for f in befunde))
eigen.write_text(SCHEMA_FIXTURE_UNSER, encoding="utf-8")
pruefe_dass("eigene Zusaetze - vierter Enum-Wert, eigener Typ - sind still",
pruefe(root) == [])
pruefe_dass("mit nur einer Baseline gibt es nichts zu vergleichen",
not any(f.startswith("ABGLEICH:") for f in pruefe(root)))