Skip to main content

8. Audit, historie en technische uitgangspunten

Deze sectie bevat generieke uitgangspunten die over meerdere tabellen heen gelden.

8.1 Auditvelden en soft delete

  • LiveViewAudit: auditabele meekijksessies worden op sessieniveau vastgelegd met meekijker, rolcontext, bekeken gebruiker, oefenrun en start-/eindmoment.
  • Gebruik waar relevant CreatedAtUtc, UpdatedAtUtc, IsActive, IsDeleted, DeactivatedAtUtc en actorvelden voor historie en herstelbaarheid. Niet elk domein gebruikt hetzelfde lifecyclepatroon: sommige tabellen zijn append-only, sommige gebruiken IsActive en andere IsDeleted of soft delete.
  • Voor relatie- en autorisatietabellen geldt dat records in principe niet hard verwijderd worden.
  • Soft delete en statuswaarden moeten in queries en indexering expliciet meegenomen worden.
  • De relationele database is niet internet facing. Alleen de backend/webserver maakt via een interne backendverbinding verbinding met de database.
  • Gewone applicatielogs hebben binnen de initiële scope een technische bewaartermijn van 1 jaar. Voor herleidbaarheid mogen deze logs waar relevant de interne Users.Id bevatten, maar zij gaan terughoudend om met directe persoonsgegevens zoals volledige e-mailadressen of naamgegevens.
  • Voor account lifecycle-acties zoals aanmaken, verwijderen en anonimiseren bestaat daarnaast een apart applicatielogbestand met beperkte toegang. Dit accountlogkanaal is aanvullend op relationele historie en wordt niet als gewone domeintabel gemodelleerd.

8.2 Enumeraties en statuswaarden

  • RelationshipInvitations.Status: Pending, Accepted, Rejected, Expired.
  • CategoryHistory.ActionType en ExerciseHistory.ActionType: gesloten vaste waardesets in code en database. CategoryHistory gebruikt ten minste CREATE, UPDATE_NAME, UPDATE_ICON, UPDATE_COLOR, SOFT_DELETE, RESTORE, MIGRATE_SOURCE_TO_TARGET en MIGRATE_TARGET_FROM_SOURCE. ExerciseHistory gebruikt ten minste CREATE, COPY_FROM_PARENT, UPDATE_NAME, UPDATE_ICON, UPDATE_MODULE_CONFIGURATION, UPDATE_IS_ACTIVE en MODULE_MIGRATION_SOURCE_TO_TARGET.
  • Moduleinterface en configuratiepayloads: de technische contracten rond beschrijving, configuratie, generatie, vraagweergave en bewerken worden primair in FO/TO/SRS vastgelegd; database-opslag sluit daarop aan via ExerciseModules, Exercises en de JSON/base64-payloads op run- en voortgangsniveau.
  • SystemMessages.Type, SystemMessages.CreatedBySystemComponent, SystemMessages.EntityType, RelationshipEvents.EventType, PrivateMessageThreadEvents.EventType, TicketDiscussionMessages.MessageType, TicketDiscussionMessages.Visibility, TicketReopenRequests.RequestSource en de lookup-tabellen TicketStatuses, TicketResolutionTypes en TicketCategories worden centraal beheerd en gedocumenteerd. SystemMessages.EntityType is daarbij een gesloten vaste set in code en database met exact de waarden RelationshipInvitation, Ticket, PrivateMessageThread en SharedExercise.
  • TicketHistory.ActionType is een gesloten vaste set in code en database. Leesbare historytekst wordt in de applicatie geformatteerd op basis van ActionType, OldValue en NewValue. SiteFeatureToggles gebruikt een centrale lijst van bekende technische sleutels, waaronder ook AccessibilityEnabled.

8.3 Scheduler- en cleanup-afhankelijkheden

TickerQ beheert de eigen persistence. Het document beschrijft alleen de functionele afhankelijkheden, zoals cleanup van afgehandelde uitnodigingen uit het overzicht openstaande uitnodigingen, het automatisch verlopen van onbeantwoorde uitnodigingen, periodieke verwerking van verlopen ticket-heropentermijnen en andere expliciet geconfigureerde cleanup-processen.

  • Scheduler- en cleanupjobs zijn functioneel niet-blokkerend voor de primaire gebruikersprocessen. Bij een fout worden uitzonderingen gelogd en volgt retrygedrag via de standaardmechanismen van TickerQ.
  • Wanneer een cleanup- of opruimtaak tijdelijk niet slaagt, wordt de betreffende data bij een volgende geplande run opnieuw beoordeeld en alsnog verwerkt zodra de taak weer succesvol draait.
  • Gesloten meldingen blijven binnen de initiële scope onbeperkt bewaard. Er geldt binnen de huidige scope geen automatische retentie- of opschoonjob die gesloten meldingen definitief verwijdert of archiveert.
  • Retentie en cleanup worden per domein expliciet bepaald. Voor gesloten meldingen geldt binnen de initiële scope geen automatische retentie- of opschoonjob; andere domeinen kunnen afzonderlijke, configureerbare termijnen kennen. Voor privéberichten geldt binnen de huidige scope een retentietermijn van drie maanden; systeemberichten volgen niet automatisch dezelfde opschoonregel omdat zij functioneel onderdeel blijven van mailbox-, relatie- en meldingsflows. Voor tijdelijke docent-testoefeningen geldt expliciet dat deze als testrun mogen worden opgeslagen om generieke afhandeling, realtime meekijken en uniforme voortgangsverwerking te ondersteunen, maar via geplande cleanup weer verwijderd worden en niet als blijvende leerlinghistorie bedoeld zijn.
  • De accountverwijderflow binnen OefenHub voert directe anonimisering en samenhangende cleanup uit voor relaties, uitnodigingen en toegangscontexten. Niet-afgeronde exercise runs worden daarbij niet afgerond of hersteld, maar blijven historisch aanwezig als niet-voltooide run onder het geanonimiseerde account.

8.4 Module-interface en technische contextobjecten

De persistente opslag volgt bewust een hybride model: uniforme, relationeel relevante kerngegevens blijven querybaar in reguliere tabellen, terwijl modulespecifieke configuratie-, vraag- en antwoordpayloads generiek in JSON/base64 worden opgeslagen. Technische oefenmodules worden daarbij opgevat als losse librarycomponenten achter een vaste strategy-interface. Beschikbare modules worden administratief beheerd in ExerciseModules en niet runtime ontdekt uit assemblies of codepaden. De generieke moduleplatform- en contractregels staan in Oefenmodules — moduleplatform en contract; de eerste concrete module-uitwerking staat in Optellen & Aftrekken (simpel). Technische contextobjecten zoals GenerateModuleExercisesContext, ShowModuleQuestionContext en vergelijkbare helpercontracten behoren tot de applicatie- en module-interface en niet tot het relationele datamodel. Hun exacte interne inhoud wordt primair vastgelegd in Functioneel Ontwerp, Technisch Ontwerp, Software Requirements Specification, moduledossiers en code. Persistente opslag blijft generiek en centraal verlopen via ExerciseModules, Exercises, ExerciseRuns en ExerciseRunProgress, aangevuld met de bijbehorende JSON/base64-payloads. Uitbreiding van zulke contextobjecten leidt daarom niet automatisch tot nieuwe databasekolommen of tabellen; alleen wanneer een nieuw gegeven persistent, historisch relevant, querybaar, rapporteerbaar of auditbaar moet zijn, wordt beoordeeld of uitbreiding van het relationele model nodig is.

Module- en schemaherleidbaarheid binnen configuratiepayloads wordt daarom in de payload zelf vastgelegd, bijvoorbeeld met moduleKey en schemaVersion. Deze waarden zijn bedoeld om modules backwards-compatible interpretatie te laten uitvoeren en zijn geen aanleiding voor een aparte generieke databasekolom zolang zij niet platformbreed querybaar of rapporteerbaar hoeven te zijn.

8.5 Frontend-context en schermspecifieke contextkeuze

De keuze van actieve frontendweergave en schermcontext bij multi-role gebruikers behoort primair tot applicatielogica en niet tot het relationele datamodel. Rollen en roltoekenningen worden persistent vastgelegd via Roles en UserRoles, terwijl de samenstelling van frontpages en schermcontexten volgt uit vaste business rules, prioriteitsvolgorden en codegedreven schermlogica. Alleen contextkeuzes die gebruikersspecifiek, persistent en functioneel relevant zijn voor later gebruik, filtering of hervatting, worden expliciet opgeslagen, zoals een geselecteerd docentniveau in UserSettings. Uitbreiding van schermspecifieke contextlogica leidt daarom niet automatisch tot nieuwe tabellen of kolommen.

8.6 Realtime-mechaniek

SignalR is binnen OefenHub de gekozen realtime-mechaniek voor live meekijken, aanwezigheidssignalen en andere directe server-naar-clientupdates die niet wachten op een volgende paginaverversing of traditionele request-responsecyclus.

  • De database legt de daarvoor historisch of auditbaar relevante effecten vast, zoals LiveViewAudit, actuele runvoortgang en server-side opgeslagen status, maar modelleert SignalR zelf niet als relationele entiteit.
  • Realtimegedrag blijft daarmee bewust gescheiden van persistente domeindata: de pushverbinding is technisch, terwijl opslag van runvoortgang, audit en historie relationeel en herleidbaar blijft.

8.7 Scheiding van communicatie- en notificatiedomeinen

OefenHub kent bewust meerdere communicatie- en notificatiedomeinen met een verschillend doel, lifecycle-gedrag en opslagmodel. Deze domeinen mogen in de GUI dicht bij elkaar liggen, maar blijven relationeel en technisch gescheiden.

  • SystemMessages zijn mailboxgerichte systeemberichten die gekoppeld kunnen zijn aan een domeinactie of domeinentiteit en onderdeel blijven van relatie-, uitnodigings- en meldingsflows.
  • PrivateMessages vormen het aparte conversatie- en threaddomein tussen gekoppelde gebruikers, inclusief eigen participants, thread-events en een aparte retentie- en cleanup-lijn.
  • SiteNotifications zijn beheerder-gestuurde, planbare UI-notificaties voor doelgroepen of sitebrede communicatie en vormen nadrukkelijk geen mailboxstroom zoals SystemMessages.
  • Deze scheiding voorkomt dat mailboxgedrag, threadgedrag en sitebrede UI-meldingen in één generiek model worden geperst, terwijl de functionele samenhang in de applicatielaag behouden blijft.

8.8 Relatie-audit en afhankelijkheden

8.8.1 Uitnodigingslifecycle en cleanup

  • RelationshipInvitations.Status kent de waarden Pending, Accepted, Rejected en Expired.
  • Pending-uitnodigingen naar onbekende e-mailadressen kunnen gedurende 7 dagen aan een nieuw geregistreerd account met hetzelfde genormaliseerde e-mailadres worden gekoppeld.
  • Wanneer de koppeling na registratie slaagt, wordt de uitnodiging functioneel resolvebaar voor de nieuwe gebruiker en kan een SystemMessages-record worden aangemaakt.
  • Onbeantwoorde uitnodigingen die buiten de geldigheidsduur vallen, worden via scheduler/cleanup naar Expired gezet.
  • Afgehandelde uitnodigingen blijven historisch bewaard, maar worden niet als openstaand item weergegeven.
  • Cleanup is functioneel niet-blokkerend: wanneer een cleanup-run tijdelijk faalt, wordt de uitnodiging bij een volgende run opnieuw beoordeeld.

8.8.2 Audit en events

  • Relatie- en uitnodigingsacties worden auditbaar vastgelegd via RelationshipEvents.
  • Uitnodiging versturen, externe uitnodiging versturen, registratiekoppeling, rol-incompatibele afwijzing, acceptatie, afwijzing, verlopen, relatie-aanmaak, ontkoppeling en ontkoppelverzoek zijn afzonderlijke eventtypen.
  • Relatieontkoppeling is soft-deactivatie en geen hard delete.
  • Het intrekken of toevoegen van een rol tijdens acceptatie van een relatie-uitnodiging blijft auditbaar via UserRoles en de bijbehorende domeinevents.

8.8.3 Scheiding van domeinen

  • Inkomende relatie-uitnodigingen worden via SystemMessages aangeboden, maar de uitnodigingsstatus blijft eigendom van RelationshipInvitations.
  • Privéberichten, systeemberichten, site-notificaties en relatie-events blijven gescheiden domeinen, ook wanneer zij in de GUI dicht bij elkaar liggen.
  • Een relatie-uitnodiging is geen privébericht en maakt geen PrivateMessageThread of PrivateMessages aan.

8.9 Generieke technische uitgangspunten

8.9.1 Readmodels, tellers en badges

  • Mailbox- en actietellers zijn afgeleide waarden uit domeintabellen en worden niet als zelfstandige tellerrecords opgeslagen, tenzij een later technisch ontwerp expliciet een cache- of projectietabel introduceert.
  • Ongelezen berichttellers worden afgeleid uit SystemMessages.ReadAtUtc, participant-readstate en relevante privéthread-events.
  • Meldingenacties zoals Wacht op mij worden afgeleid uit actuele ticketstatus en gebruikerscontext, niet uit een aparte actietellertabel.
  • Een tijdelijk verborgen badge, bijvoorbeeld tijdens een actieve leerling-oefenrun, wijzigt de onderliggende ongelezen- of actiestatus niet.

8.9.2 Browserwaarden en clientside markers

  • Browserwaarden voor toegankelijkheid en OncePerBrowser-systeemnotificaties zijn technische hulpmiddelen en geen primaire domeinopslag.
  • Browserwaarden mogen geen persoonsgegevens, autorisatiedata, rolcontext, e-mailadressen of vrije profielgegevens bevatten.
  • Ongeldige of corrupte browserwaarden worden genegeerd en mogen geen technische fout veroorzaken.
  • Voor SiteNotifications wordt geen server-side per-gebruiker-gezienlog of aparte seen-tabel geïntroduceerd.

8.9.3 Ticket- en communicatieaudit

  • Ticketdiscussie, ticketgeschiedenis, ticketclosures, heropenverzoeken, assignments en doorzetregistraties hebben ieder een eigen verantwoordelijkheid en mogen niet als onderlinge vervanging worden gebruikt.
  • TicketHistory blijft compact en actiontype-gedreven; lange toelichtingen horen in discussie of gespecialiseerde subtabellen.
  • PrivateMessageThreadEvents zijn systeemachtige conversatie-events en geen SystemMessages of gewone PrivateMessages.
  • SystemMessages, PrivateMessages, TicketDiscussionMessages en SiteNotifications blijven gescheiden domeinen, ook wanneer zij in de GUI dicht bij elkaar worden gepresenteerd.

8.9.4 Geen hard delete in generieke gebruikersflows

  • Relaties, privéthreads, tickets, ticketdiscussie, ticketclosures, heropenverzoeken, site-notificaties en profielbasisrecords worden niet hard verwijderd door reguliere gebruikersflows.
  • Verwijderen in de mailbox is participantgebonden zichtbaarheid, geen fysieke verwijdering van de thread of berichten.
  • Accountverwijdering blijft een aparte lifecycleflow met anonimisering en gecontroleerde cleanup; profiel- en voorkeurenusecases voeren geen accountverwijdering uit.

8.10 Account-lifecycle en technische context

8.10.1 Accountprovisioning en sessieverwerking

Eerste accountprovisioning, provisioningfouten, dubbele ExternalId-situaties, ontbrekende verplichte identity-providerclaims en pogingen tot toegang met een gedeactiveerd intern account moeten technisch herleidbaar zijn via applicatie- of accountlogging. Deze logging vormt geen nieuw relationeel domeinmodel.

Voor reguliere login- en sessieverwerking wordt geen aparte sessietabel geïntroduceerd. De identity-provider- en applicatiesessie zijn technische authenticatiecontext; alleen domeinrelevante effecten zoals Users.LastSeenAtUtc, UserSettings-herstel of provisioningmutaties worden persistent in het datamodel vastgelegd.

8.10.2 Frontendcontext, routeguard en account lifecycle-logging

De actieve frontendcontext wordt na login server-side bepaald op basis van Users, actieve rollen, instellingen en business rules. Deze contextkeuze wordt niet als aparte sessie-entiteit opgeslagen. Een geauthenticeerde gebruiker zonder actieve rolcontext krijgt uitsluitend de beperkte context zonder rol; directe routes naar rolgebonden pagina’s worden door routeguard en backendautorisatie geblokkeerd.

Accountverwijdering en anonimisering moeten technisch herleidbaar zijn voordat de verwerking als afgerond geldt. Account lifecycle-logging mag geen wachtwoorden, tokens, secrets of credentialgegevens bevatten. Falen van de verwijder- of anonimiseerflow mag niet leiden tot een half geanonimiseerde accounttoestand met reguliere toegang.

Technische/logische eventnamen uit de accountusecases, zoals DuplicateExternalIdDetected, AccountAnonymized of LogoutProcessed, impliceren niet automatisch een nieuwe eventtabel. Zij beschrijven ontwerp-, audit- of loggingmomenten die via bestaande audit-, history- of logmechanismen verwerkt kunnen worden.

8.11 Beheerder- en ouder-/voogdcontext in audit

OnderwerpAanscherping
BeheerredenHoog-impact beheeracties zoals accountdeactivatie, anonimisering, categorie-/modulemigratie, eigendomsoverdracht en geforceerde toegang vereisen een expliciete reden.
BeheerhistorieBeheeracties leggen minimaal actor, rolcontext, tijdstip, domein, actie, objectreferentie en relevante oude/nieuwe waarde vast.
Recente beheerwijzigingenDe beheerder-frontpage toont recente beheerwijzigingen als readmodel over beheerhistorie. Raadplegen van dit blok veroorzaakt geen mutatie.
SysteeminstellingenlogWijzigingen aan systeeminstellingen en cacheverversing zijn auditbaar en herleidbaar tot de uitvoerende beheerder of systeemtaak.
Frontpage/contentgeschiedenisContentblok-, footer-, URL-, popup- en systeemberichttemplatewijzigingen houden veldspecifieke historie bij.
LiveViewAuditOuder-/voogd-live-meekijksessies worden net als docent-live-meekijken auditbaar vastgelegd met viewer, rolcontext, kind, run, start en einde.
Geen audit door read-only raadplegingOverzichten, details, geschiedenisweergaven en readmodels veroorzaken geen domeinmutaties, behalve noodzakelijke technische logging waar expliciet toegestaan.

8.12 Ouder-/voogdresultaten en live-audit

OnderwerpAanscherping
ResultaatraadplegingNormale resultaatraadpleging door ouder/voogd is read-only en vereist niet standaard een eigen auditrecord per schermrefresh. Beperkte securitylogging is passend bij geweigerde toegang of foutanalyse.
PDF-exportPDF-export kan als technische exportpoging worden gelogd, maar maakt geen verplicht permanent documentrecord en wijzigt geen run.
LiveViewAuditAlleen daadwerkelijke start van live meekijken opent LiveViewAudit; beschikbaarheidsbepaling en online-overzicht doen dit niet.
Idempotent eindeBeëindiging vult EndedAtUtc alleen wanneer het live-auditrecord nog open is. Dubbele beëindiging leidt niet tot extra functionele mutatie.
Geen realtime-eventauditIndividuele SignalR-updates worden niet als aparte database-auditregels gemodelleerd; de actuele voortgang blijft in ExerciseRunProgress en de sessie in LiveViewAudit.