CodamAIDocs
Themafertig

Die zwei Hash-Ketten

Jedes Objekt hat seine eigene Kette, jeder Mandant sein Journal: was ein Hash verknüpft, was im Journal steht und was die Prüfung findet.

Ausprägungen
Objektkette einer VorlagenvarianteObjektkette eines DokumentsJournal eines MandantenJournal des Systems

Worum es geht

Eine Hash-Kette ist eine Liste, in der jeder Eintrag den Hash seines Vorgängers enthält. Änderst du einen Eintrag, passt sein Hash nicht mehr zum nächsten Eintrag. Die Kette ist an dieser Stelle gebrochen, und jede Prüfung findet das.

CRMS führt zwei Arten solcher Ketten:

  • Die Objektkette: eine je Vorlagenvariante und eine je Dokumentkette. Sie hält die Geschichte genau eines Objekts.
  • Das Journal: eines je Mandant und eines für das System. Es hält jedes nachweisrelevante Ereignis aller Objekte dieses Mandanten.

Wie die Ketten zusammenhängen

flowchart LR
  subgraph O["Objektkette (eine Dokumentkette)"]
    R1["Revision 1"] --> R2["Revision 2"] --> H["Legal Hold gesetzt"]
  end
  subgraph J["Journal des Mandanten"]
    J1["template-published"] --> J2["document-stored"] --> J3["document-stored"] --> J4["document-legal-hold-set"]
  end
  R1 -. "Hash" .-> J2
  R2 -. "Hash" .-> J3
  H -. "Hash" .-> J4
  J4 -. "Kopf" .-> A["Anker mit Zeitstempel"]

Jeder Journal-Eintrag trägt den Hash des Objektketten-Eintrags, über den er berichtet. Wer eine Revision ändern will, muss also ihre Objektkette und das Journal umschreiben. Und das Journal ist nach außen verankert, siehe Zeitstempel und Anker.

Was ein Hash verknüpft

Beide Ketten folgen derselben Regel:

hash_n = SHA-256( kanonisch(metadaten_n) ‖ inhalts_hash_n ‖ hash_n-1 )
  • kanonisch heißt: Die Metadaten werden als JSON nach RFC 8785 geschrieben. Gleiche Daten ergeben immer dieselben Bytes, egal in welcher Reihenfolge die Felder standen.
  • inhalts_hash ist bei einer Dokumentrevision der Hash der Dokument-Bytes. Im Journal ist es der Hash des gemeldeten Objektketten-Eintrags.
  • hash_n-1 ist der Hash des Vorgängers. Der erste Eintrag einer Kette hat keinen und nimmt stattdessen lauter Null-Bytes.
  • ‖ heißt: hintereinander gehängt. Die beiden Hashes gehen als rohe Bytes ein, nicht als Text.

Das Verfahren steht vor jedem Hash (sha-256:…). Wer Metadaten, Inhalt und Kette hat, kann jeden Hash selbst nachrechnen, mit nichts als einem JSON-Kanonisierer und SHA-256.

Was in einen Eintrag der Dokumentkette eingeht

Was gehasht wird
Metadaten eines Eintrags (kanonisiert, dann gehasht)
Metadaten
{
  "chain": "5d0c…",            ← die Kette
  "sequence": 2,               ← Position, ab 1, ohne Lücken
  "type": "REVISION",          ← REVISION, LEGAL_HOLD_SET, LEGAL_HOLD_RELEASED, ERASED
  "revision": "7c1e…",         ← die betroffene Revision
  "actor": "u-3f2a…",          ← wer es ausgelöst hat
  "at": "2026-10-17T09:12:44.123456Z",
  "reason": null,              ← Grund als Schlüssel, bei Hold und Löschung
  "attributes": { "number": 2, "template": "audit-report", "payloadHash": "sha-256:…", … }
}

Jeder Eintrag trägt den Inhalts-Hash der Revision, um die es geht. Auch eine Löschung oder ein Legal Hold ist damit fest an genau ein Dokument gebunden.

Eine Dokumentkette kennt vier Eintragsarten:

ArtBedeutung
REVISIONein endgültiges Dokument wurde als nächste Revision gespeichert
LEGAL_HOLD_SETein Legal Hold wurde auf die Kette gesetzt
LEGAL_HOLD_RELEASEDder Legal Hold wurde aufgehoben
ERASEDdie Bytes einer Revision wurden gelöscht, ihr Schlüssel vernichtet

Ob eine Revision gelöscht ist oder ein Hold gilt, speichert CRMS nicht in einem Feld daneben. Es liest den Zustand aus den Einträgen. Was dort nicht steht, gilt nicht.

Was im Journal steht

Jedes Journal ist eine eigene Kette. Ein Eintrag nennt Art, betroffenes Objekt, Benutzer und Zeit:

ArtBetroffenes ObjektWann
template-revisiontemplate-variant:<id>eine Vorlagen-Revision wurde eingereicht
template-approvedtemplate-variant:<id>eine Freigabestufe wurde erteilt
template-rejectedtemplate-variant:<id>eine Revision wurde abgelehnt
template-publishedtemplate-variant:<id>eine Revision wurde veröffentlicht
template-supersededtemplate-variant:<id>die veröffentlichte Revision wurde abgelöst
template-retiredtemplate-variant:<id>die veröffentlichte Revision wurde ohne Nachfolger zurückgezogen
document-storeddocument-chain:<id>ein endgültiges Dokument wurde gespeichert
document-legal-hold-setdocument-chain:<id>ein Legal Hold wurde gesetzt
document-legal-hold-releaseddocument-chain:<id>ein Legal Hold wurde aufgehoben
document-eraseddocument-chain:<id>eine Revision wurde gelöscht
document-key-destroyeddocument-key:<id>ein Dokumentschlüssel wurde vernichtet
document-readdocument-chain:<id>ein endgültiges Dokument wurde gelesen, nur mit CODAMAI_CRMS_AUDIT_READS=true

Wie die Vorlagen-Einträge entstehen, steht unter Lebenszyklus einer Vorlage.

Wo die Ketten liegen

WasWo
Journal eines Mandantenin der Datenbank des Mandanten
Journal des Systems (Schlüssel system)in der System-Datenbank
Dokumentkettenbeim Journal ihres Eigentümers

Alle Tabellen werden nur ergänzt, nie geändert: Einträge kommen dazu, keiner wird überschrieben oder gelöscht. Nur der Kopf des Journals, der die letzte Nummer und den letzten Hash hält, wird fortgeschrieben. Mehr unter Datenbank.

Gleichzeitiges Schreiben

Zwei Vorgänge im selben Mandanten schreiben nacheinander ins Journal. CRMS sperrt dafür den Kopf des Journals, bis die Transaktion endet. Rollt ein Vorgang zurück, verschwindet sein Journal-Eintrag mit ihm.

Was trifft aufeinanderWas passiert
zwei Schreiber in einem bestehenden Journalder zweite wartet und schreibt danach
zwei Schreiber legen im selben Moment dasselbe neue Journal aneiner bekommt 409 journal-concurrent-change und kann es erneut versuchen
zwei Schreiber hängen gleichzeitig an dieselbe Dokumentkette aneiner bekommt 409 document-concurrent-change und kann es erneut versuchen

Eine Kette verzweigt sich nie.

Was die Prüfung einer Kette findet

Die Prüfung rechnet jeden Hash vom ersten Eintrag an neu und vergleicht ihn mit dem gespeicherten.

Manipulationgefunden durch
ein Byte eines Eintrags geändertdie Kette selbst
ein Eintrag entfernt oder die Reihenfolge vertauschtdie Kette selbst
der letzte Eintrag des Journals entferntden Vergleich mit dem Kopf
das Journal ab einer Stelle stimmig neu berechnet, Kopf eingeschlossennur den Anker mit Zeitstempel

Fallen

Weiter

Suchen