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
+48
View File
@@ -97,6 +97,54 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
## ✅ Erledigt
- [x] **🧾 Beglaubigte Alt-Lücken: Dauer-Alarm im Gegenbuch beendet** (2026-08-26)
- **Ausgangslage.** Das Gegenbuch auf Prod meldete stündlich `exit=2`. Die
CRM-Prüfung lieferte `valid: false` wegen **6 struktureller Lücken**
(IDs 33, 44, 45, 922, 1434, 2583).
- **Diagnose: harmlos.** Jede Lücke liegt innerhalb eines Schwungs von
Einträgen mit **identischer Sekunde**, betrifft nur `/login` und
`/refresh`, kein Eintrag fehlt (kein 404), `tamperedEntries` leer. Das ist
die Signatur der Race-Condition, die am 19.08. mit `AuditChainLock`
geschlossen wurde (R166-01). Bestätigt durch die Zeilen selbst: Eintrag
2583 stammt vom 21.08., ist aber noch `hashVersion=1` auf Prod lief zu
dem Zeitpunkt also der alte Stand.
- **Das eigentliche Problem war nicht die Lücke, sondern der Dauer-Alarm.**
Diese Lücken sind nicht heilbar: die Verkettung ist gebrochen, die Inhalte
sind unversehrt. Ohne Änderung hätte das Gegenbuch für immer Alarm gemeldet
und ein Signal, das immer schreit, warnt nicht mehr. Dieselbe Klasse wie
R162, R174, R179, R182, R183-02.
- **Lösung: Beglaubigung statt Unterdrückung.** Eine Lücke zählt nicht mehr
als offener Befund, wenn beides gilt: sie liegt im **versiegelten Bereich**
UND steht im **Vorbefund des Siegel-Markers**, also im Zustand, den der
Betreiber beim Siegeln ausdrücklich festgeschrieben hat. Der Vorbefund
liegt in `changesBefore` und ist ab Version 3 mitgehasht die Liste lässt
sich ohne `AUDIT_HMAC_KEY` nicht nachträglich erweitern (gleiche
Absicherung wie beim Löschungs-Manifest, R171-01).
- Beglaubigt heißt **nicht verschwunden**: die Lücken bleiben in `chainGaps`,
stehen zusätzlich in neuem Feld `attestedGaps` und werden im Bericht
ausdrücklich benannt.
- **Nebenbefund derselben Klasse mitbehoben:** `backlogSealStatus` meldete
`nicht_noetig` ("Es gibt keine unsignierten Alteinträge"), wenn
`v3FromId === null` das bedeutet aber das **Gegenteil**: dass überhaupt
nichts signiert ist, etwa weil `AUDIT_HMAC_KEY` fehlt. Ein vollständig
unsigniertes Log bekam damit Entwarnung für genau den Zustand mit der
geringsten Beweiskraft. Die Bedingung hängt jetzt an der Zahl der
unsignierten Zeilen. R183-03 (unauflösbare Warnung bei frisch signiertem
Log) bleibt behoben per Regressionstest geprüft.
- **Getestet über 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 Lücke** nach dem Siegeln → `valid:false`;
**gesiegelte Altzeile verändert** → Siegel `gebrochen`, nichts mehr
beglaubigt; **Beglaubigungsliste im Marker gefälscht** (DB-Schreibrecht,
kein Schlüssel) → Marker ungültig, Status `entfernt`, `valid:false`.
- Doku: Abschnitt „Wenn der erste Lauf `exit=2` meldet" in
`tools/audit-notary/README.md` inklusive der Warnung, **vor** dem
Siegeln zu prüfen, was man da festschreibt.
- Dateien: `backend/src/services/audit.service.ts`,
`backend/src/controllers/auditLog.controller.ts`,
`frontend/src/services/api.ts`, `tools/audit-notary/README.md`
- [x] **🔓 Dienstkonto-Flag: Gate in beide Richtungen, richtige Rechte-Domaene (Pentest R184-01/-02)** (2026-08-24)
- **R184-01** Setzen war gegatet, **Entfernen nicht**. Und das Entfernen ist
der gefaehrlichere Weg: Der Heartbeat-Wachhund fragt `isServiceAccount: