11. Audit, historie en technische uitgangspunten
Deze pagina is de ERD-inzoomlaag voor het databasehoofdstuk 08_audit-historie-en-technische-uitgangspunten.md. De tabeldefinities in dat brondocument blijven leidend; deze pagina maakt de terugkerende audit-, history-, snapshot- en niet-FK-patronen expliciet.
Dit is bewust geen klassiek domein met één eigen tabelcluster. Het audit- en historiehoofdstuk werkt als een overkoepelende validatielaag over de andere ERD-pagina's heen. Gebruik deze pagina wanneer je wilt controleren of een relatie een harde FK, soft link, soft link + snapshot, logische verwijzing, snapshot, enumwaarde, payloadkeuze of afgeleid readmodel is.
11.1 Doel van deze inzoomlaag
- Uitleggen welke audit- en historypatronen in meerdere domeinen terugkomen.
- Voorkomen dat logische verwijzingen als harde database-FK's worden gelezen.
- Het verschil tonen tussen actuele bronrecords, append-only history, snapshots, technische logging en afgeleide readmodels.
- Vastleggen wanneer
Userseen actor is, en wanneerUserseigenaar, deelnemer, ontvanger, leerling, docent of beheerder is.
11.2 Overkoepelend patroon
De hoofdlijn is: brondata blijft in domeintabellen, wijzigingen en lifecycle-events worden in gespecialiseerde history/audittabellen bewaard, en readmodels, tellers en badges worden afgeleid. Technische logging ondersteunt analyse, maar vervangt geen domeintabel.
11.3 Patronenkaart
| Patroon | Bedoeling | Typische tabellen / velden | ERD-regel |
|---|---|---|---|
Actor-audit naar Users | Vastleggen wie een handeling uitvoerde. | ChangedByUserId, CreatedByUserId, ClosedByUserId, AssignedByUserId, DeletedByUserId | Over modulegrenzen documenteren als soft link naar identity.Users.Id; teken hoogstens als contextlijn en benoem dat dit actorcontext is. |
| Append-only history | Reconstructie van wijzigingen of events. | CategoryHistory, ExerciseHistory, RelationshipEvents, TicketHistory, *History-tabellen | Teken vanuit het hoofdrecord naar history; history wijzigt het hoofdrecord niet terug. |
| Formele lifecycle-bron | Een specifieke lifecycle-stap heeft een eigen bronrecord. | TicketClosures, TicketReopenRequests, TicketAssignments, TicketForwardedToTeacher | Teken als gespecialiseerde childrecords van het hoofdrecord. |
| FK + snapshot | FK blijft relationeel binnen dezelfde module, snapshot bewaart historische leesbaarheid. | Domeininterne status- of bronrelatie met tekstsnapshot. | Teken de FK hard en benoem de snapshot expliciet als tekstuele momentopname. |
| Soft link + snapshot | Cross-module verwijzing blijft functioneel, snapshot bewaart historische leesbaarheid. | LiveViewAudit.ViewerRoleId + ViewerRoleNameSnapshot, runcontext op ExerciseRuns | Niet als harde FK tekenen; benoem soft link en snapshot samen. |
| Snapshot zonder FK | Historische of technische context zonder stabiele bronrelatie. | TicketTechnicalSnapshots.UserAgent, PageUrl, IpAddress, UserRolesSnapshot | Teken niet als FK; beschrijf als snapshotvelden. |
| Logische verwijzing | Applicatielogica routeert naar een beperkt domeinobjecttype. | SystemMessages.EntityType + EntityId | Gebruik flowchart of gestippelde contextlijn; geen harde FK suggereren. |
| Gesloten enum/sleutelset | Kleine stabiele waardeset in code/database. | ActionType, EventType, Visibility, RequestSource, FeatureKey, SettingKey | Niet als lookup tekenen tenzij het brondocument expliciet een lookup-tabel definieert. |
| JSON/base64-payload | Modulespecifieke of sterk variabele data. | Exercises.ConfigurationPayload, ExerciseRuns/ExerciseRunProgress payloadvelden | Niet relationeel uitsplitsen in ERD, tenzij een waarde platformbreed querybaar moet worden. |
| Afgeleid readmodel | Overzicht, teller, badge of filterwaarde. | mailboxbadges, Guardian*ReadModel, frontpagetellers | Niet als brontabel tekenen zolang geen fysieke tabel is gedefinieerd. |
11.4 Representative audit-ERD's
11.4.1 Actor-audit rond beheerbare inhoud
Dit patroon gebruikt compacte voorbeeldlijnen voor domeinen waar een hoofdrecord een of meer historyrecords heeft. De volledige FK-lijst blijft in de relatie-index staan. Harde relaties binnen hetzelfde domein worden met een normale pijl getoond; actorverwijzingen naar identity.Users.Id worden bewust als gestippelde soft link getoond.
Leesregel: de actorverwijzing naar Users betekent hier “wie voerde de wijziging uit”. Over modulegrenzen is dit een soft actorlink naar identity.Users.Id, geen harde database-FK en geen functioneel eigenaarschap van de categorie, oefening, template of notificatie.
11.4.2 Ticket-lifecycle als auditmodel
Actorverwijzingen binnen ticket-lifecycle-tabellen worden niet als harde ERD-relatie getekend. Zij zijn soft links naar identity.Users.Id en worden als volgt gelezen:
Leesregel: Tickets.StatusId geeft de actuele processtatus. De lifecycle-details staan in gespecialiseerde tabellen. Actorvelden naar Users zijn soft links over de modulegrens. Een TicketHistory-regel is compacte audit en vervangt geen discussiebericht, sluitrecord of heropenverzoek.
11.4.3 Live meekijken met soft links + snapshot
LiveViewAudit ligt in het live-monitoringdomein en verwijst over modulegrenzen naar identity, authorization en practice. Deze verwijzingen worden daarom als soft links gedocumenteerd. ViewerRoleNameSnapshot maakt de auditregel leesbaar wanneer rolnamen of rolcontexten wijzigen of wanneer de actuele rolcontext niet meer beschikbaar is.
11.4.4 Relatie-events versus relatiebron
Een uitnodiging, acceptatie, afwijzing, verlopen uitnodiging of ontkoppeling is niet alleen een statuswijziging. Relevante gebeurtenissen blijven via RelationshipEvents herleidbaar. Actor- en targetvelden naar Users of rolvelden naar Roles worden over modulegrenzen als soft links gedocumenteerd.
11.5 Logische verwijzingen zonder harde FK
Sommige verwijzingen zijn bewust niet als database-FK gemodelleerd omdat één veld naar meerdere toegestane domeintypen kan verwijzen, of omdat de bron een codegedreven sleutel is.
ERD-regel: teken deze route niet als gewone crow's-foot FK. Gebruik een flowchart of gestippelde contextlijn en benoem expliciet dat applicatielogica de combinatie EntityType + EntityId valideert.
11.6 Snapshots en historische leesbaarheid
Snapshots zijn bedoeld om de naamgeving, context of actorweergave vast te houden zoals die was op het moment waarop een relevante gebeurtenis ontstond. Zij zijn dus geen vrije duplicatie van actuele brondata, maar beschermen resultaatweergave, geschiedenis, audit en export tegen latere wijzigingen in namen, rollen, modules of relaties.
| Snapshottype | Voorbeeld | Waarom geen live herberekening? |
|---|---|---|
| Rolnaam-snapshot | LiveViewAudit.ViewerRoleNameSnapshot bij soft link naar authorization.Roles.Id | Auditregels moeten begrijpelijk blijven als rolnamen wijzigen of de actuele rolcontext niet beschikbaar is. |
| Technische ticketsnapshot | TicketTechnicalSnapshots.UserAgent, PageUrl, IpAddress, UserRolesSnapshot | De technische context hoort bij het moment van melden en kan later anders zijn. |
| Gedeelde-oefening-snapshot | tekstuele niveau-, categorie- en oefeningcontext op SharedExercises | Een gedeelde oefening moet historisch herkenbaar blijven na latere naams- of categoriemigraties. |
| Runcontext | niveau/categorie/oefening/modulecontext op ExerciseRuns | Resultaten, geschiedenis en PDF-export moeten de historische uitvoering tonen, niet de actuele configuratie. |
| Accountanonimisering | geanonimiseerde identiteit in historie/audit | Historie moet reconstrueerbaar blijven zonder actuele persoonsgegevens te tonen. |
Aanvullende uitgangspunten:
- Het voorkeursmoment voor het vullen van run-snapshots is bij het starten van de run, omdat dan de oefencontext daadwerkelijk wordt vastgelegd. Alleen met expliciete onderbouwing kan een later moment, zoals afronden, worden gekozen.
- Snapshotvelden worden na vastlegging in principe niet bijgewerkt. Een uitzondering is accountanonimisering: dan mogen persoonsgegevens in snapshots worden overschreven met vooraf gedefinieerde anonimiseringswaarden.
- Zolang de oorspronkelijke soft links naar niveau, categorie, oefening of technische module bruikbaar en compatibel zijn, mag de applicatie actuele live data gebruiken voor technische verwerking. De snapshotvelden zijn de fallback voor historische weergave wanneer live naamgeving, moduleversie of context niet langer betrouwbaar of beschikbaar is.
- PDF-export is geen permanente documentbron. Een PDF wordt live gegenereerd uit de database en mag daarna technisch worden opgeruimd, bijvoorbeeld via een periodieke cleanupjob. De database, inclusief snapshots, blijft de bron voor een latere hergeneratie.
Snapshots zijn dus geen duplicaatdata zonder reden. Zij beschermen historische interpretatie en maken hergeneratie van resultaatweergaven mogelijk wanneer actuele context is veranderd.
11.7 JSON/base64 en module-specifieke payloads
De payloadvelden zijn bron voor modulespecifieke inhoud, maar worden niet volledig relationeel uitgesplitst. Alleen waarden die platformbreed nodig zijn voor zoeken, filtering, autorisatie, statistiek, geschiedenis, PDF-export of live meekijken horen als gewone kolommen in het model.
11.8 Readmodels, tellers en badges
| Afgeleide waarde | Bron | Geen zelfstandige bron omdat... |
|---|---|---|
| Ongelezen berichtenbadge | SystemMessages.ReadAtUtc, participant-readstate, thread-events | De teller verandert door onderliggende berichten/readstate. |
| Meldingen “Wacht op mij” | Tickets.CreatedByUserId + Tickets.Status | Actie-indicatie is een view op actuele ticketstatus. |
| Frontpage-samenvattingen | rollen, relaties, niveaus, runs, tickets, beheerrecords | De betekenis moet per blok gedefinieerd worden, maar de teller is geen domeinbron. |
| Ouder-/voogdresultaatreadmodels | afgeronde niet-test ExerciseRuns binnen actieve relatiecontext | Er ontstaat geen aparte ouderresultaatdata. |
| Livebeschikbaarheid | actieve relatie/context + actieve run + featurestatus | Beschikbaarheid start nog geen live-auditrecord. |
Een readmodel mag later technisch gecachet worden, maar dat verandert de functionele bron niet. Een cache of projectietabel moet dan apart worden gedocumenteerd.
11.9 Cleanup, retentie en scheduler
Scheduler- en cleanupjobs zijn functioneel niet-blokkerend. Wanneer een run faalt, moet de volgende run dezelfde kandidaten opnieuw kunnen vinden op basis van actuele databasecriteria. Het verlopen van een ticket-heropentermijn maakt bijvoorbeeld geen nieuw TicketClosures-record aan; de bestaande actuele sluitregistratie blijft de formele bron.
11.10 Checklist voor volgende ERD-wijzigingen
Gebruik deze checklist bij iedere nieuwe tabel of nieuwe ERD-lijn:
| Vraag | Gevolg voor ERD |
|---|---|
| Is dit een echte relationele afhankelijkheid binnen dezelfde module, DbContext en schema? | Teken als harde FK in een ERD. |
Is dit een actorveld naar Users over een modulegrens? | Documenteer als soft link; teken hoogstens als contextlijn. |
| Is dit een polymorfe of codegedreven verwijzing? | Niet als harde FK tekenen; gebruik flowchart/contextlijn. |
| Is dit een historisch tekstbeeld naast een domeininterne FK? | Benoem als FK + snapshot. |
| Is dit een historisch tekstbeeld naast een cross-module verwijzing? | Benoem als soft link + snapshot. |
| Is dit een enum of gesloten sleutelset? | Alleen als tabel tekenen wanneer het brondocument een lookup-tabel definieert. |
| Is dit een afgeleide teller of badge? | Niet als bron-ERD tekenen; documenteer de bronquery of readmodelregel. |
| Is dit modulespecifieke payload? | Niet uitsplitsen tenzij de waarde platformbreed querybaar of auditbaar moet worden. |
| Is dit technische logging buiten de database? | Niet als relationele tabel modelleren, tenzij later een fysieke log-/audit-tabel wordt toegevoegd. |
11.11 Raakvlakken met andere ERD-pagina's
| Vervolgpagina | Gebruik wanneer... |
|---|---|
| Identiteit en autorisatie | je wilt bepalen of Users een identiteit, actor, ontvanger, eigenaar of rolcontext vertegenwoordigt. |
| Relatiebeheer | je uitnodigingsstatussen, relatie-events of soft-deactivatie wilt volgen. |
| Docentstructuur en leerlingtoegang | je categorie-/oefeninghistorie, modulemigratie of live-audit wilt begrijpen. |
| Oefenruns, delen en voortgang | je JSON/base64, historische runcontext, gedeelde-oefening-snapshots of PDF-bronnen wilt controleren. |
| Communicatie en notificaties | je SystemMessages.EntityType + EntityId, mailboxreadmodels of thread-events wilt bekijken. |
| Ticket- en meldingsbeheer | je het verschil tussen discussie, history, closures, reopen requests en snapshots wilt controleren. |
| Configuratie en contentbeheer | je beheerbare brondata, historytabellen, templates, popups, notificaties en featurekeys wilt bekijken. |
| Relatie-index | je wilt controleren of een concrete kolom als harde FK, soft link, soft link + snapshot of logische verwijzing is gedocumenteerd. |