AARs nachgeholt: vier Pflichtfaelle, die ich uebergangen habe

roadmap.md verlangt einen AAR nach jedem Deploy mit Uebergabe und nach
Incidents. Gestern und heute gab es vier solche Ereignisse - Produktionsausfall
durch rc.2, Verlust der DKIM-Eintraege, Game-Host-Deploy ueber zwei Uebergaben,
volle Wurzelpartition auf rohana mit gerissenem Spiegel. Geschrieben wurde
zunaechst keiner. Der letzte AAR im Repo stammte vom 16.08.

Dokumentiert habe ich stattdessen ausfuehrlich IN den Issues. Das ist nicht
wertlos, aber es ist nicht der Ort, an dem AGENTS.md die Lehren sucht - und die
Folge war messbar: dieselben Fallen wurden an einem Tag mehrfach neu entdeckt.

#0040 um eine Rueckmeldung ans Framework ergaenzt: Die Vorgaben wurden dort
eingehalten, wo ein Werkzeug widerspricht (validate, gen_status, Spiegel), und
uebergangen, wo nur Prosa sie verlangt. Mit Vorschlaegen fuers Refinement -
einschliesslich der ehrlichen Alternative, die Definition von Vorfall enger zu
fassen statt strenger zu kontrollieren.
This commit is contained in:
Thore Cimbal
2026-08-20 12:00:00 +00:00
parent 897755cdef
commit 242e839eb6
6 changed files with 376 additions and 2 deletions
+5 -2
View File
@@ -119,6 +119,9 @@ _none active_
| [0022](docs/adr/0022-upstream-anschluss-durch-einmaligen-merge.md) | accepted | ADR-0022: Anschluss an Upstream durch einen einmaligen Merge, nicht durch einen geteilten Graft |
| [0023](docs/adr/0023-fremdhistorie-von-der-git-hygiene-ausnehmen.md) | accepted | ADR-0023: Fremde Historie von der Git-Hygiene ausnehmen — erklärt, nicht global |
## Open AARs (0)
## Open AARs (4)
_none — nothing awaiting harvest_
- [AAR — `v0.6.0-rc.2` brach die Raumliste in Produktion](docs/aar/2026-08-19-rc2-raumliste-produktion.md)
- [AAR — Drei DKIM-Einträge verschwanden bei der DMARC-Härtung](docs/aar/2026-08-20-dkim-verlust-zonenaenderung.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)
@@ -0,0 +1,81 @@
---
type: aar
status: open
date: 2026-08-19
related:
- "docs/issues/0099-threadnet-web-12-upstream-sicherheitsfixes-lassen-sich-nicht-m.md"
- "docs/adr/0022-upstream-anschluss-durch-einmaligen-merge.md"
---
# AAR — `v0.6.0-rc.2` brach die Raumliste in Produktion
## 1. Was geplant war
Den Upstream-Anschluss (ADR-0022) an einem echten Client abnehmen. Der Merge war gebaut,
die CI grün, das Image lag in der Registry. Erwartet war: Tag-Bump, sorb prüft ClamAV und
die Call-Teilnehmerliste, fertig.
## 2. Was geschah
| Zeit (2026-08-19) | |
|---|---|
| 15:13 | `v0.6.0-rc.2` ausgerollt, `/version` bestätigt |
| ~15:17 | sorb meldet Rageshake, `label = react-soft-crash` |
| 15:2x | Ursache gefunden, Rückrollung auf `v0.5.4` |
| 15:3x | Produktion wieder gesund |
`RoomListItemViewModel.ts` rief `SettingsStore.getValue("feature_room_list_sections")` auf
einen Labs-Schalter, den Upstream **entfernt** hatte. `getValue` wirft bei unbekanntem
Schlüssel — und zwar bei **jedem** Raumlisteneintrag. Der Client war unbenutzbar.
Die Zeile war ein Rest meiner Konfliktauflösung: Upstream hatte das Feature samt
`canMoveToSection` gestrichen, ich hatte überall korrekt Upstreams Seite genommen — Menü,
View, Snapshot, Typen — und diese eine `const`-Zeile stehen lassen. Sie wurde tree-weit
von niemandem gelesen.
## 3. Warum der Unterschied
**Spec issue, nicht code issue.** Der Fehler war statisch sichtbar: `tsc` meldete ihn
zweifach (`TS2345` unbekannter Schlüssel, `TS6133` ungenutzte Konstante). Er ist nur nie
gefragt worden — der CI-Job `web` führt ausschließlich `pnpm --dir apps/web build` aus.
webpack entfernt Typen, ohne sie zu prüfen; ein unbekannter Einstellungsschlüssel ist zur
Bauzeit bloß ein String.
Die eigentliche Fehlleistung liegt davor: **Ich habe einen grünen Build als
Korrektheitsaussage weitergegeben.** Er war eine Aussage über Baubarkeit, mehr nicht.
Dazu eine Blindstelle in ADR-0022 selbst: Sie begründet den Merge damit, dass Git künftig
meldet, wenn Upstream unsere Zeilen wegschiebt. Das hat gehalten. Ein Drei-Wege-Merge
meldet aber **nicht** den umgekehrten Fall — beide Seiten überleben die Auflösung, und nur
eine ergibt noch Sinn.
## 4. Lehren
- **Ein grüner Build sagt nichts über Korrektheit**, solange niemand typprüft. Das gilt
für jedes Repo, in dem Bau und Prüfung getrennt sind.
- **Nach einer Konfliktauflösung auf *überlebende* Reste prüfen, nicht nur auf verlorene
Zeilen.** Der billigste Hebel ist die Typprüfung; sie fand beide Reste sofort.
- **Wo Zeichenketten statt Typen im Spiel sind** — Einstellungsschlüssel, Feature-Namen,
Übersetzungs-IDs — reicht die Typprüfung nicht. Dort braucht es einen Abgleich gegen die
jeweilige Registry. Einmalig gefahren: 135 abgefragte gegen 152 registrierte
Einstellungen, genau eine Leiche.
- **Beim Bau einer Prüfung fast derselbe Fehler:** Das erste `grep "error TS"` hätte nie
gegriffen, weil nx auch in der Pipe färbt und zwischen `error` und `TS` eine
Escape-Sequenz steht. Ein Tor, das immer grün ist, ist schlimmer als keines.
## 5. Maßnahmen
| Maßnahme | Stand |
|---|---|
| Job `typecheck`, den `docker_web` als `needs` führt | erledigt (`ThreadNet-Web:8ca03fe`) |
| Auf den ganzen Baum ausgeweitet, auch die Desktop-Jobs | erledigt (`ac343e9`) |
| Tor in beide Richtungen belegt (mit Fehler rot, ohne grün, bei leerer Ausgabe rot) | erledigt |
| Verlauf und Lehre in #0099 festgehalten | erledigt |
| `docs/axion1337-fork.md`: Abschnitt „Upstream-Update seit dem Anschluss" | erledigt (`6632ad5`) |
## 6. Offen
- Der Maßstab des Tores ist „kein Fehler außerhalb von `node_modules`", weil Upstream
v1.12.26 selbst nicht typrein ist (`matrix-js-sdk@42.2.0`, drei Fehler in der eigenen
Quelle). Fällt das bei einem Upstream-Wechsel weg, verschärft sich der Job von allein.
- Unit-Tests laufen weiterhin in **keiner** Pipeline. Das Tor prüft Typen, nicht Verhalten.
@@ -0,0 +1,72 @@
---
type: aar
status: open
date: 2026-08-20
related:
- "docs/issues/0102-dmarc-und-mail-haertung-der-zone-axion1337-chat.md"
---
# AAR — Drei DKIM-Einträge verschwanden bei der DMARC-Härtung
## 1. Was geplant war
`_dmarc.axion1337.chat` vom geteilten IONOS-CNAME (`p=none`) auf einen eigenen TXT-Record
mit `p=quarantine` umstellen und die sieben Service-Namen mit Null-MX und `-all`
stilllegen (#0102). Erwartete Wirkung: gefälschte Mail wird abgewiesen, echte nicht.
## 2. Was geschah
Die Einträge wurden gesetzt und waren korrekt. Bei der Gegenprobe fehlten jedoch **alle
drei DKIM-Selektoren** der Zone (`s1-ionos`, `s2-ionos`, `s42582890`) — NXDOMAIN bei zwei
unabhängigen DoH-Anbietern, für CNAME und TXT. Vierzig Minuten zuvor waren sie noch
gemessen worden.
Nach erneutem Aktivieren lösen `s1-ionos` und `s2-ionos` wieder bis zum Schlüssel auf
(403 Zeichen). Der dritte zeigt auf ein Ziel, das IONOS selbst nicht veröffentlicht —
folgenlos, weil mit `s1`/`s2` signiert wird.
## 3. Warum der Unterschied
**Intent issue.** Bei der Bearbeitung der Zone gingen Einträge verloren, die niemand
anfassen wollte. Die Auftragsbeschreibung nannte vier Einträge; dass eine Zonen-Bearbeitung
benachbarte Records mitnehmen kann, stand nirgends — und es gab kein Werkzeug, das
widersprochen hätte.
Verschärfend: **Der Verlust wäre nicht aufgefallen.** Direkte Zustellung besteht DMARC über
SPF-Ausrichtung — Authentik sendet über IONOS-SMTP, Absender- und Header-Domain stimmen
überein. Erst bei **Weiterleitungen** bricht SPF, und ohne DKIM gibt es keinen Rückfall.
Mit `p=quarantine` landet solche Mail dann im Spam: kein Ausfall, keine Fehlermeldung, nur
Nutzer, die nichts bekommen.
## 4. Lehren
- **Nach jeder Zonen-Bearbeitung die ganze Zone gegenprüfen, nicht nur die geänderten
Einträge.** Derselbe Vorfall in anderer Form: `mrtc`-A-Record, 2026-08-16 (eigener AAR).
Zweimal dieselbe Klasse in vier Tagen.
- **Ein vorhandener CNAME belegt nichts.** `s42582890` zeigt ins Leere. Prüfungen müssen
bis zum **Schlüssel** auflösen.
- ⚠️ **Im Lab wird Port 53 abgefangen.** Eine Anfrage an `192.0.2.99` (TEST-NET, kann nicht
antworten) liefert ein Ergebnis. Vier vermeintlich autoritative `dig`-Messungen zeigten
deshalb einen längst überholten Stand — beinahe wäre daraus die Meldung „der Record ist
gar nicht angekommen" geworden. **Belastbar ist von innen nur DoH über HTTPS.**
- **Berichtsadressen gehören in dieselbe Zone.** Ein `rua=` auf eine fremde Domain braucht
zusätzlich `<domain>._report._dmarc.<zieldomain>`; ohne den senden die meisten Empfänger
stillschweigend gar nichts — und ausbleibende Berichte sehen aus wie „keine Probleme".
## 5. Maßnahmen
| Maßnahme | Stand |
|---|---|
| DKIM wieder aktiviert, `s1`/`s2` lösen bis zum Schlüssel auf | erledigt |
| `pruefe-dns.sh` prüft DKIM — bis zum Schlüssel, nicht bis zum CNAME | erledigt (`notfallhandbuch:b6ee7fd`) |
| `pruefe-dns.sh` warnt **vor** seinen Messungen, wenn Port 53 abgefangen wird | erledigt |
| `dns-soll.md`: Soll-Stand für DMARC und die sieben Service-Namen | erledigt (`91d7106`) |
| `dns-soll.md`: Falschangabe korrigiert (Wartungsmeldungen senden unter `.de`) | erledigt |
| Abnahme: Authentik-Testmail kam nachweislich an | erledigt |
## 6. Offen
- `fo=1` im DMARC-Record ist wirkungslos, weil kein `ruf=` gesetzt ist.
- `axion1337.de` trägt `p=reject` **ohne `rua=`** — dort greift die Regel, aber niemand
sieht ihre Wirkung.
- Nach Auswertung der ersten Berichte: `p=quarantine``p=reject`.
@@ -0,0 +1,86 @@
---
type: aar
status: open
date: 2026-08-20
related:
- "docs/issues/0002-game-01-host-von-cfgmon-aus-nicht-erreichbar-2.md"
- "docs/issues/0083-gitops-50-monitoring-deploy-geaenderte-configs-greifen.md"
---
# AAR — Game-Host-Anbindung: Deploy über zwei Übergaben, Ursache war nie die Firewall
## 1. Was geplant war
Zwei Scrape-Targets waren seit dem ersten Scrape nie `up` (#0002, seit 2026-08-01). Das
Issue nannte die Hetzner-Cloud-Firewall als wahrscheinliche Ursache und empfahl, den Host
in den vSwitch aufzunehmen. Geplant war: Targets auf die private Adresse umstellen, fertig.
## 2. Was geschah
Der Host **war** längst im vSwitch (`10.0.0.4`, Ports 22/80/443 offen). Trotzdem
antworteten die Exporter nicht. `docker ps` zeigte den Grund:
```
node-exporter … 9100/tcp ← kein 0.0.0.0-Mapping
cadvisor … 8080/tcp ← ebenso
```
**Die Ports waren gar nicht veröffentlicht** — die Exporter waren nur im Docker-Netz
erreichbar. Über die öffentliche IP hätten sie nie geantwortet, mit oder ohne Firewall.
Nebenbefund: Auf dem Host liefen **zwei** Sammler-Sätze und ein vollständiger eigener
Prometheus/Grafana/Loki-Stack, in den niemand hineinsah.
Der Deploy lief über **zwei Übergaben in beide Richtungen**: eine an den
Game-Host-Betreiber (Rückbau auf reine Sammler), eine zurück (zentrale Seite). Beide nach
`.gitlab/issue_templates/Deploy-Übergabe.md`.
## 3. Warum der Unterschied
**Spec issue.** Die Vermutung „Firewall" stand seit dem 01.08. unwidersprochen im Issue
und wurde nie gegen den Host geprüft — es hatte niemand `docker ps` gelesen. Die
Eingrenzung von damals (Ports 22/8080/9100 im Timeout, kein Refused) war korrekt gemessen,
aber falsch gedeutet: Ein Paketfilter *und* ein nicht veröffentlichter Port sehen von
außen gleich aus.
Zweiter Fund derselben Klasse: Der Exporter auf `9531` hat **drei Monate lang nie eine
Metrik geliefert** (Client-API mit Application-Key über http). Das zugehörige Dashboard war
seit jeher leer — und niemandem ist es aufgefallen, weil niemand geprüft hat, ob seine
Metriken überhaupt existieren.
## 4. Lehren
- **Ein leeres Dashboard ist ein Befund, kein Zustand.** Wer eines anlegt, prüft seine
Queries gegen die laufende Instanz — sonst steht dort jahrelang nichts, ohne dass es
jemand merkt.
- **Von außen gemessene Symptome erlauben mehrere Ursachen.** „Port antwortet nicht"
unterscheidet nicht zwischen Filter, fehlender Veröffentlichung und totem Dienst. Erst
ein Blick auf den Host trennt das.
- **`metric_relabel_configs` gehören dem scrapenden Prometheus.** Baut man einen lokalen
Stack zurück, wandert dessen Label-Konzept mit — sonst fällt es ersatzlos weg.
- ⚠️ **Der Entwicklungstunnel führt bei `cfgmon.lab:9090` und `:3100` auf eine ANDERE
Maschine.** Der operating-Stack ist von außen nur aus dem matrix-Cluster über
`10.0.0.3` erreichbar. Das hat an diesem Tag zwei Diagnosen ins Leere laufen lassen,
einmal beim Monitoring und einmal beim DNS.
- **`--force-recreate` ist für Prometheus überholt** — das Verzeichnis wird gemountet.
Beide Übergaben trugen die Anweisung aus #0083 veraltet weiter.
## 5. Maßnahmen
| Maßnahme | Stand |
|---|---|
| Targets auf `10.0.0.4`, cadvisor auf 8081 (8080 von coolify-proxy belegt) | erledigt (`threadnet-operating:a07c6ed`) |
| `gameserver-rules.yml`: Join Container-UUID → Klarname | erledigt |
| Dashboard neu gebaut, alle 11 Queries vorab gegen Prometheus geprüft | erledigt (`fa26393`) |
| Die letzten fünf Einzeldatei-Mounts auf Verzeichnisse (#0083) | erledigt (`7b25b6e`) |
| #0002 geschlossen, #0083 mit dem tatsächlichen Reststand fortgeschrieben | erledigt |
## 6. Offen
- `matrix/flux2.json` ist teilweise leer: Es nutzt `gotk_reconcile_condition` und
`gotk_suspend_status`, die es in dieser Flux-Fassung nicht mehr gibt. Unabhängig von
diesem Vorgang, eigener Befund.
- Der `ptla_`-Key des alten Exporters kann rotiert und gelöscht werden — er wird nirgends
mehr gebraucht. Liegt beim Game-Host-Betreiber.
- Einmalig nötig auf CFGMON: `docker compose up -d --force-recreate loki alloy
matrix-alerts release-watch cve-exporter`, damit die neuen Verzeichnis-Mounts greifen.
@@ -0,0 +1,84 @@
---
type: aar
status: open
date: 2026-08-20
related:
- "docs/issues/0099-threadnet-web-12-upstream-sicherheitsfixes-lassen-sich-nicht-m.md"
- "docs/adr/0023-fremdhistorie-von-der-git-hygiene-ausnehmen.md"
---
# AAR — rohana lief voll, der ThreadNet-Web-Spiegel riss
## 1. Was geplant war
Nach dem Upstream-Merge sollte der Push-Mirror den neuen Stand nach Gitea tragen — ein
Vorgang, der bis dahin unbemerkt lief.
## 2. Was geschah
Der Spiegel scheiterte reproduzierbar. Der Erstschub trägt **70.269 Commits und rund
600 MB**; der Worker bricht mit `HTTP 499` ab. Ich habe daraufhin von Hand in Etappen
gepusht — mit sorbs ausdrücklicher Freigabe, weil „nie direkt nach Gitea" sonst gilt.
Der Verlauf war zäh und meine Diagnosen zweimal falsch:
| Annahme | Widerlegt durch |
|---|---|
| „zu große Pakete, also stückeln" | ein Push mit **6 Objekten** scheiterte ebenfalls |
| „festes Zeitlimit von ~60 s" | ein Push über **95 s** lief durch |
Die tatsächliche Ursache: **Das Ziel-Repo war auf 9,6 GB aufgebläht** (lokal 649 MB), voll
mit Rückständen jedes abgebrochenen Pushs — meine eingeschlossen. Jeder Push zahlte die
Erreichbarkeitsprüfung über diesen Haufen, unabhängig von seiner Nutzlast.
Verschärfend: Die **Wurzelpartition von rohana war zu 100 % voll** (37 GB von 39 GB, 262 MB
frei). Damit konnte auch `git gc` nicht mehr durchlaufen — die Reparatur scheiterte an dem
Zustand, den sie beheben sollte.
Nach Aufräumen und sauberem Repack (Repo auf 451 MB) liefen die Etappen durch. Auffällig
dabei: Derselbe Schritt scheiterte im ersten Versuch nach 3.734 Sekunden und gelang im
zweiten in **einer** Sekunde.
## 3. Warum der Unterschied
**Intent issue.** ADR-0022 hat die Folgen des Merges für die *Historie* durchdacht, nicht
die für die *Infrastruktur*. Dass ein Spiegel 70.000 fremde Commits in einem Zug
übertragen muss und dass das Ziel-Repo dafür in gutem Zustand sein muss, stand in keiner
Entscheidung.
Mein eigener Beitrag: Ich habe nach dem ersten Fehlschlag **dreimal mit großen Paketen
weiterprobiert**, statt zuerst den Zustand der Gegenseite zu messen. Jeder dieser Versuche
hat den Müllberg vergrößert.
## 4. Lehren
- **Vor einem großen Push den Zustand des Ziels messen**, nicht nur die eigene Seite:
`count-objects -vH`, freier Platz. Ein Spiegel, der seit Monaten nie repackt wurde, ist
kein passives Ziel.
- **Bei sporadischer Serverlast ist wiederholen die richtige Antwort, nicht verkleinern.**
Mit Halbieren wäre der Lauf nie fertig geworden.
- **Wiederholte Fehlschläge hinterlassen Spuren.** Ein abgebrochener Git-Push ist nicht
folgenlos — er lädt Objekte hoch, die niemand referenziert und niemand aufräumt.
- **`git gc` braucht freien Platz.** Auf einer vollen Platte ist es kein Ausweg.
- ⚠️ **rohana trägt Gitea UND die Container-Registry.** Eine volle Platte dort heißt: kein
neues Image, kein Release. Dass währenddessen nichts ausfiel, war Glück — die Images
wurden vorher gepusht.
## 5. Maßnahmen
| Maßnahme | Stand |
|---|---|
| Platte aufgeräumt, Repo repackt (9,6 GB → 451 MB) | erledigt (sorb) |
| Spiegel wieder gleichauf, `main` = `8ca03fe`, gegengeprüft | erledigt |
| Spiegel-Worker trägt seither wieder von selbst nach | belegt am Doku-Push `6632ad5` |
| ADR-0023: fremde Historie von der Git-Hygiene ausnehmen | erledigt |
## 6. Offen
- **Kein Alarm auf freien Plattenplatz von rohana.** Der Host wird nicht überwacht — die
Fülle fiel nur auf, weil ein Push scheiterte. `HostLowDisk` existiert in
`alerts.yml`, deckt aber nur die Hosts ab, die Metriken liefern.
- Ob weitere Repos auf rohana in schlechtem Pack-Zustand sind, ist ungeprüft.
- Die Frage, ob der Spiegel die **volle** Upstream-Historie tragen muss oder ein
Stand ab dem Merge-Commit genügt, ist offen. Sie entscheidet, ob dieser Vorfall
wiederkommt.
@@ -33,3 +33,51 @@ mit Feldtest-Evidenz aus Gate 2 des Design-Dokuments:
10. ADR-Pflicht bei dauerhaften Ausnahmen fehlt upstream
11. Stillstandsprüfungs-Prinzipien als Muster für eine
Laufzeit-Prüf-Familie neben validate.py
## Rückmeldung 2026-08-20 — der Prozess hält nicht von allein
Aus einer langen Arbeitssitzung (Upstream-Anschluss, Game-Host-Anbindung, DMARC-Härtung,
Egress-Rückbau). Sorbs Beobachtung, und sie trifft zu: **Die Vorgaben wurden eingehalten,
wo ein Werkzeug sie erzwingt, und übergangen, wo nur Prosa sie verlangt.**
### Der Befund
`roadmap.md` schreibt vor: *„AAR nach jedem Deploy mit Übergabe und nach Incidents."*
An diesem und dem Vortag gab es **vier** AAR-pflichtige Ereignisse — ein
Produktionsausfall, ein Verlust von DKIM-Einträgen, ein Deploy über zwei Übergaben und
eine volle Wurzelpartition mit gerissenem Spiegel. Geschrieben wurde zunächst **keiner**.
Nachgeholt erst auf Zuruf.
Dagegen wurde **kein einziges Mal** vergessen: Frontmatter nach `schema.yaml`, `STATUS.md`
neu erzeugen, Issues spiegeln, Commit-Konvention. Der Unterschied ist nicht Sorgfalt,
sondern **wer widerspricht**: `validate.py` und `gen_status.py --check` färben die Pipeline
rot. Der AAR-Pflicht widerspricht niemand.
### Warum das mehr als Buchhaltung ist
AGENTS.md sagt: *„Before proposing options, read the relevant ADRs and AARs first — past
decisions and learnings are input, not trivia."* Wenn Erkenntnisse nur als Anhänge in
einzelnen Issues liegen, stehen sie nicht dort, wo das Verfahren sie sucht.
Die Folge war an diesem Tag messbar: Dieselben Fallen wurden **mehrfach neu entdeckt**
dass der Entwicklungstunnel bei `cfgmon.lab` auf eine andere Maschine zeigt (zweimal), und
dreimal ein eigenes Messwerkzeug, das stillschweigend Unsinn lieferte (`grep` gegen
Farbcodes, `/dev/tcp` in dash, `dig` gegen einen abgefangenen Port 53). Zeit, die zweimal
bezahlt wurde.
### Vorschläge fürs Refinement
1. **`gen_status.py` zeigt offene AARs bereits an** — aber nichts prüft, ob es *fehlende*
gibt. Denkbar: eine Prüfung, die Deploy-Übergabe-Issues und geschlossene Vorfälle ohne
zugehörigen AAR meldet. Sie bräuchte eine Kennzeichnung, was als Vorfall gilt.
2. **Vorfall als eigener Status oder eigenes Feld am Issue.** Heute lässt sich nicht
maschinell erkennen, dass #0099 einen Produktionsausfall enthielt.
3. **Die Kadenz-Regel steht in `roadmap.md`, nicht in AGENTS.md.** Wer AGENTS.md liest —
und darauf verweist jedes `CLAUDE.md` — findet die AAR-Pflicht nicht.
4. **Ehrliche Alternative:** Wenn eine Regel dauerhaft nur bei Nachfrage befolgt wird, ist
auch zu prüfen, ob sie in dieser Form richtig ist. Vier AARs an einem Tag sind viel;
vielleicht braucht es eine engere Definition von „Vorfall" statt strengerer Kontrolle.
⚠️ Diese Rückmeldung stammt von der Seite, die den Prozess gebrochen hat. Sie ist kein
Beleg dafür, dass die Regel schlecht ist — nur dafür, dass sie sich ohne Werkzeug nicht
durchsetzt.