Skip to content

Lägg till dokumenterad rättelse för auditloggens integritet#101

Merged
perNyfelt merged 3 commits into
mainfrom
feature/dokumenterad-auditlogg-rattelse
Jul 24, 2026
Merged

Lägg till dokumenterad rättelse för auditloggens integritet#101
perNyfelt merged 3 commits into
mainfrom
feature/dokumenterad-auditlogg-rattelse

Conversation

@perNyfelt

Copy link
Copy Markdown
Member

Summary

  • Adds AuditLogService.recordIntegrityRemediation(companyId, auditLogId, reason): documents that a specific audit-log row's hash mismatch is a known, already-explained historical anomaly (e.g. data corrupted by a bug that's since been fixed) - without ever touching the broken row's entry_hash/previous_hash. It appends a new, normally-chained INTEGRITY_REMEDIATION entry pointing at the row it explains.
  • validateIntegrity() / validateIntegrity(companyId) now exclude documented rows from the critical list (so they stop blocking ReportIntegrityService-gated operations), while listDocumentedExceptions(companyId) surfaces them separately for transparency. Structural chain-head problems always stay critical, since they can't be tied to (and explained away for) one specific row.
  • Remediation is guarded: the target row must exist and currently have an undocumented integrity problem, and a reason is required - this is for documenting real, already-diagnosed anomalies, not for pre-emptively annotating healthy rows.
  • Adds a "Data Integrity & Corrections" section to AGENTS.md codifying the rule this whole thing exists because of: never patch ledger tables directly with SQL to fix bad data, corrections go through the app's own domain operations (same append-only pattern as correction vouchers), and a discovered hash-integrity violation gets documented via this new method - never by rewriting hashes or by calling rebuildIntegrityChain/repairIntegrityForAllCompanies as general-purpose repair tools (those are one-time, migration-gated fixes for specific historical bugs, not a mechanism to silence future violations).

Test plan

  • ./gradlew build - full suite green (compile, CodeNarc, Spotless, all tests)
  • New tests in AuditLogServiceTest: remediating a genuinely broken row moves it from critical to documented and the chain still validates; remediating a healthy row is rejected; remediating an unknown row id is rejected; a blank reason is rejected

🤖 Generated with Claude Code

perNyfelt and others added 3 commits July 24, 2026 13:31
Ett tidigare fel (felaktigt tolkade ingående balanser, sedan patchade
direkt i databasen) visade att vi saknade ett sätt att förklara en känd,
redan utredd avvikelse i hashkedjan utan att antingen (a) tysta
integritetskontrollen permanent genom att skriva om entry_hash/
previous_hash, vilket gör kedjan om intet, eller (b) låta ett förklarat
historiskt fel blockera rapportexport och andra åtgärder för evigt.

recordIntegrityRemediation(companyId, auditLogId, reason) löser detta
genom att lägga till en ny, normalt kedjad INTEGRITY_REMEDIATION-post som
pekar ut och förklarar den trasiga raden - originalraden rörs aldrig.
validateIntegrity() delas nu upp i två nivåer: en dokumenterad avvikelse
räknas inte längre som kritisk (och blockerar därmed inte
ReportIntegrityService), men förblir synlig via den nya
listDocumentedExceptions(companyId). Rättelsen kräver att målraden
faktiskt har ett odokumenterat problem just nu, så metoden inte kan
användas för att stämpla friska rader i förväg.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Skriver ner regeln som den nya rättelsemekanismen (och det bakomliggande
incidenten den löser) bygger på, så framtida agent-sessioner inte
återupprepar misstaget: aldrig patcha ledgertabeller direkt med SQL,
rättelser går via applikationens egna domänoperationer (samma
append-only-mönster som korrigeringsverifikationer), och en upptäckt
hashintegritetsavvikelse dokumenteras via
AuditLogService.recordIntegrityRemediation() - inte genom att skriva om
entry_hash/previous_hash eller köra rebuildIntegrityChain()/
repairIntegrityForAllCompanies() som allmänna reparationsverktyg.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
P1: exemptionen nycklades bara på auditLogId, så när en rad väl fått en
rättelsepost flyttades varje nuvarande OCH framtida hash-/föregående-
hash-avvikelse på den raden ur validateIntegrity()'s blockerande lista -
oavsett om den nya avvikelsen hade något med den ursprungligen förklarade
att göra. En andra, annorlunda manipulation av en redan rättad rad
maskerades därmed tyst.

recordIntegrityRemediation() fångar nu radens exakta avvikande tillstånd
vid rättelsetillfället (lagrat entry_hash, lagrat previous_hash, samt
den hash som innehållet borde ge om det inte var manipulerat) och lagrar
det i rättelsepostens details-text. checkIntegrityForCompany() undantar
en rad bara när dess NUVARANDE tillstånd exakt matchar en tidigare
dokumenterad avvikelse - ändras raden igen, på något sätt, matchar inget
tidigare dokumenterat tillstånd längre och den flaggas som kritisk på
nytt.

Nytt regressionstest bevisar detta: rättar en rad, manipulerar den sedan
en andra gång på ett annat sätt, och verifierar att den dyker upp i den
kritiska listan igen i stället för att tystas av den första rättelsen.

Justerade också en CodeNarc-varning (onödig null-check) och synkade det
kvarvarande hårdkodade "id = 1" i validateIntegrityDetectsTamperedAuditRow
med idOfRow-hjälparen, enligt granskningens förslag.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@perNyfelt
perNyfelt merged commit 63f0119 into main Jul 24, 2026
3 checks passed
@perNyfelt
perNyfelt deleted the feature/dokumenterad-auditlogg-rattelse branch July 24, 2026 13:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant