Beglaubigte Alt-Luecken: Dauer-Alarm im Gegenbuch beendet

Das Gegenbuch auf Prod meldete stuendlich exit=2, weil die CRM-Pruefung
wegen 6 struktureller Luecken valid:false lieferte (IDs 33, 44, 45, 922,
1434, 2583).

Diagnose: harmlos. Jede Luecke liegt innerhalb eines Schwungs von
Eintraegen mit identischer Sekunde, betrifft nur /login und /refresh,
kein Eintrag fehlt, tamperedEntries ist leer. Das ist die Signatur der
Race-Condition, die am 19.08. mit AuditChainLock geschlossen wurde
(R166-01). Die Zeilen datieren ihren eigenen Code: Eintrag 2583 stammt
vom 21.08., ist aber noch hashVersion=1.

Das eigentliche Problem war nicht die Luecke, sondern der Dauer-Alarm.
Diese Luecken sind nicht heilbar - die Verkettung ist gebrochen, die
Inhalte sind unversehrt. Ohne Aenderung haette das Gegenbuch fuer immer
Alarm gemeldet, und ein Signal, das immer schreit, warnt nicht mehr.
Dieselbe Klasse wie R162, R174, R179, R182, R183-02.

Loesung: Beglaubigung statt Unterdrueckung. Eine Luecke zaehlt nicht mehr
als offener Befund, wenn sie im versiegelten Bereich liegt UND im
Vorbefund des Siegel-Markers steht - also im Zustand, den der Betreiber
beim Siegeln ausdruecklich festgeschrieben hat. Der Vorbefund liegt in
changesBefore und ist ab Version 3 mitgehasht; ohne AUDIT_HMAC_KEY laesst
sich die Liste nicht nachtraeglich erweitern (gleiche Absicherung wie
beim Loeschungs-Manifest, R171-01). Beglaubigt heisst nicht verschwunden:
die Luecken bleiben in chainGaps, stehen zusaetzlich in attestedGaps und
werden im Bericht ausdruecklich benannt.

Nebenbefund derselben Klasse mitbehoben: backlogSealStatus meldete
"nicht_noetig" ("Es gibt keine unsignierten Alteintraege"), wenn
v3FromId === null - das bedeutet aber das Gegenteil, naemlich dass
ueberhaupt nichts signiert ist, etwa weil AUDIT_HMAC_KEY fehlt. Ein
vollstaendig unsigniertes Log bekam damit Entwarnung fuer genau den
Zustand mit der geringsten Beweiskraft. Die Bedingung haengt jetzt an der
Zahl der unsignierten Zeilen; R183-03 bleibt behoben (Regressionstest).

Getestet ueber HTTP gegen eine eigene Wegwerf-Datenbank, nicht am Service
vorbei: Prod-Zustand nachgebaut (10 v1-Zeilen, Bruch bei id 5) ->
valid:false; nach seal-backlog -> valid:true, attestedGaps:[5]. Drei
Gegenproben: neue Luecke nach dem Siegeln -> valid:false; gesiegelte
Altzeile veraendert -> Siegel gebrochen, nichts mehr beglaubigt;
Beglaubigungsliste im Marker gefaelscht (DB-Schreibrecht, kein
Schluessel) -> Marker ungueltig, Status entfernt, valid:false.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-26 09:07:26 +02:00
co-authored by Claude Opus 5
parent e504b8be96
commit ecaeae48d4
5 changed files with 207 additions and 13 deletions
+52
View File
@@ -86,6 +86,58 @@ soll nicht versehentlich passieren.
getrennte Dienste mit getrennten Verzeichnissen und getrennten Schlüsseln.
Welche laufen, steuert `COMPOSE_PROFILES` in der `.env`.
### Wenn der erste Lauf `exit=2` meldet: den Altbestand versiegeln
Ein CRM, das schon länger läuft, hat fast immer einen **Altbestand** Einträge
aus der Zeit, bevor das Protokoll signiert wurde. Solange der nicht versiegelt
ist, meldet die Prüfung `valid: false`, und das Gegenbuch schlägt zu Recht
Alarm. Typischerweise steht dann im Log:
Hinweis: Der Altbestand ist nicht versiegelt Änderungen daran wären
nicht erkennbar.
Das ist **einmalig** zu erledigen, im CRM, nicht hier:
```bash
TOKEN=$(curl -s -X POST https://<crm>/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"…","password":"…"}' | jq -r '.data.token')
curl -s -X POST https://<crm>/api/audit-logs/seal-backlog \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"confirm":"SEAL"}' | jq
```
Dafür braucht es das Recht `audit:admin` das Gegenbuch-Dienstkonto hat es
absichtlich **nicht**. Nimm dein Administratorkonto.
**Zuerst nachsehen, was versiegelt wird.** Das Siegel schreibt den aktuellen
Zustand fest, samt aller vorhandenen Lücken. Wer blind siegelt, beglaubigt
gegebenenfalls auch eine Lücke, die von einer Löschung stammt. Deshalb vorher:
```bash
curl -s -X POST https://<crm>/api/audit-logs/verify \
-H "Authorization: Bearer $TOKEN" \
| jq '.data | {valid, chainGaps, unexplainedGaps, tamperedEntries}'
```
Stehen dort Lücken, sieh dir die betroffenen IDs und ihre Nachbarn an
(`GET /api/audit-logs/<id>`). Liegen sie jeweils **innerhalb einer Sekunde**
zusammen mit ihren Nachbarn und fehlt kein Eintrag (kein `404`), sind es
Schreibkollisionen aus paralleln Anfragen harmlos. Fehlt dagegen ein Eintrag
oder steht etwas in `tamperedEntries`, **erst klären, dann siegeln**.
Nach dem Siegeln meldet die Prüfung wieder `valid: true`, und die bekannten
Alt-Lücken erscheinen als **beglaubigt**:
Alle Einträge sind unverändert. Seit dem Bestandssiegel ist keine neue
Lücke entstanden. 6 Lücken stammen aus der Zeit vor dem Bestandssiegel
und sind darin als Vorbefund beglaubigt (IDs …).
Sie verschwinden also nicht aus dem Bericht sie zählen nur nicht mehr als
offener Befund. Jede **neue** Lücke, jede veränderte oder entfernte Altzeile
und jedes gebrochene Siegel lösen weiterhin sofort Alarm aus.
### Zugang einrichten (das brauchst du vorher)
Das Gegenbuch braucht ein **eigenes Benutzerkonto** im CRM kein Token. Der