diff --git a/FRAMEWORK-BEFUNDE.md b/FRAMEWORK-BEFUNDE.md index bf277ac..fbf5006 100644 --- a/FRAMEWORK-BEFUNDE.md +++ b/FRAMEWORK-BEFUNDE.md @@ -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 diff --git a/STATUS.md b/STATUS.md index a9c82f5..bdb8fba 100644 --- a/STATUS.md +++ b/STATUS.md @@ -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) diff --git a/docs/ledger/2026-08-21-v031-adoption-stage-2.md b/docs/ledger/2026-08-21-v031-adoption-stage-2.md index b11449e..474fbf0 100644 --- a/docs/ledger/2026-08-21-v031-adoption-stage-2.md +++ b/docs/ledger/2026-08-21-v031-adoption-stage-2.md @@ -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, diff --git a/docs/wiki/stolpersteine/massgeblich-aber-unveraenderlich.md b/docs/wiki/stolpersteine/massgeblich-aber-unveraenderlich.md new file mode 100644 index 0000000..ef6f749 --- /dev/null +++ b/docs/wiki/stolpersteine/massgeblich-aber-unveraenderlich.md @@ -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. diff --git a/docs/wiki/stolpersteine/quittung-bezeugt-aufmerksamkeit.md b/docs/wiki/stolpersteine/quittung-bezeugt-aufmerksamkeit.md new file mode 100644 index 0000000..dd08342 --- /dev/null +++ b/docs/wiki/stolpersteine/quittung-bezeugt-aufmerksamkeit.md @@ -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. diff --git a/scripts/__pycache__/pruefe_upstream_drift.cpython-312.pyc b/scripts/__pycache__/pruefe_upstream_drift.cpython-312.pyc new file mode 100644 index 0000000..0379248 Binary files /dev/null and b/scripts/__pycache__/pruefe_upstream_drift.cpython-312.pyc differ diff --git a/scripts/pruefe_upstream_drift.py b/scripts/pruefe_upstream_drift.py index d1574ca..6555458 100644 --- a/scripts/pruefe_upstream_drift.py +++ b/scripts/pruefe_upstream_drift.py @@ -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 = "" @@ -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)))