Test Mutation Report
Date: 2026-05-05
Branch: worktree-test-issue-402-legibility-preflight
Method: Manual targeted mutation (Approach A from issue #403)
Scope: 7 Tier-1 backend service domains × 5 mutations each = 35 total
For each mutation: the service method was broken, the paired test was run in isolation, and the result recorded. All mutations were reverted before proceeding to the next.
Summary: 35/35 DETECTED (100%)
Document Domain
| ID |
Test |
Mutation |
Result |
| D1 |
deleteDocument_deletesById_whenExists |
Removed documentRepository.deleteById(id) call |
DETECTED |
| D2 |
deleteDocument_throwsNotFound_whenMissing |
Removed existsById guard — always proceed to delete |
DETECTED |
| D3 |
deleteTagCascading_removesTagFromAllDocumentsAndDeletesTag |
Removed tagService.delete(tagId) at end of cascade |
DETECTED |
| D4 |
updateDocument_setsArchiveBoxAndFolder |
Removed doc.setArchiveBox(dto.getArchiveBox()) |
DETECTED |
| D5 |
createDocument_setsFileHashFromUpload_whenFileProvided |
Removed doc.setFileHash(upload.fileHash()) |
DETECTED |
Person Domain
| ID |
Test |
Mutation |
Result |
| P1 |
createPerson_savesTrimmedAlias_whenAliasIsNonBlank |
Removed .trim() — stored raw whitespace-padded alias |
DETECTED |
| P2 |
mergePersons_reassignsDocumentsAndDeletesSource |
Removed personRepository.reassignSender(sourceId, targetId) |
DETECTED |
| P3 |
findOrCreateByAlias_createsMaidenNameAlias_whenGebPresent |
Changed MAIDEN_NAME → BIRTH alias type |
DETECTED |
| P4 |
addAlias_savesWithAutoIncrementedSortOrder |
Removed +1 from findMaxSortOrder(personId) + 1 |
DETECTED |
| P5 |
removeAlias_throwsForbidden_whenAliasDoesNotBelongToPerson |
Removed ownership check — allowed cross-person alias deletion |
DETECTED |
Tag Domain
| ID |
Test |
Mutation |
Result |
| T1 |
update_savesNewName |
Removed tag.setName(dto.name()) |
DETECTED |
| T2 |
mergeTags_reassignsDocumentsReparentsChildrenAndDeletesSource |
Removed tagRepository.reparentChildren(sourceId, targetId) |
DETECTED |
| T3 |
deleteWithDescendants_deletesSubtreeDocTagsAndAllTags |
Removed tagRepository.deleteDocumentTagsByTagIds(ids) |
DETECTED |
| T4 |
update_throwsCycleDetected_whenTagIsAncestorOfProposedParent |
Flipped ancestors.contains(tagId) to !ancestors.contains(tagId) |
DETECTED |
| T5 |
update_savesColor |
Removed tag.setColor(dto.color()) |
DETECTED |
User Domain
| ID |
Test |
Mutation |
Result |
| U1 |
changePassword_updatesHash_whenCurrentPasswordCorrect |
Removed passwordEncoder.encode() — stored raw new password |
DETECTED |
| U2 |
adminUpdateUser_updatesGroups_whenGroupIdsProvided |
Removed user.setGroups(after) |
DETECTED |
| U3 |
updateProfile_allowsSameEmailForSameUser |
Removed ID equality check — threw conflict even for own email |
DETECTED |
| U4 |
adminUpdateUser_setsPassword_whenNewPasswordProvided |
Removed passwordEncoder.encode() in admin password update |
DETECTED |
| U5 |
updateProfile_setsContactToNull_whenContactIsBlank |
Removed blank→null normalization and trim — stored raw contact |
DETECTED |
Geschichte Domain
| ID |
Test |
Mutation |
Result |
| G1 |
getById_throws_NOT_FOUND_for_draft_when_user_lacks_BLOG_WRITE |
Removed draft visibility check — exposed drafts to all users |
DETECTED |
| G2 |
create_sanitizes_body_HTML_dropping_disallowed_tags |
Removed HTML sanitization — returned raw body string |
DETECTED |
| G3 |
update_sets_publishedAt_when_status_transitions_to_PUBLISHED |
Removed g.setPublishedAt(LocalDateTime.now()) on PUBLISH |
DETECTED |
| G4 |
update_clears_publishedAt_when_status_transitions_back_to_DRAFT |
Removed g.setPublishedAt(null) on DRAFT transition |
DETECTED |
| G5 |
delete_throws_NOT_FOUND_when_unknown |
Removed existsById guard — silently deleted non-existent IDs |
DETECTED |
Notification Domain
| ID |
Test |
Mutation |
Result |
| N1 |
notifyReply_createsNotificationForThreadParticipants |
Changed NotificationType.REPLY → MENTION in notifyReply |
DETECTED |
| N2 |
markRead_throwsForbidden_whenNotificationBelongsToDifferentUser |
Removed ownership check in markRead |
DETECTED |
| N3 |
markRead_marksNotificationAsRead_whenRecipientMatches |
Removed notification.setRead(true) |
DETECTED |
| N4 |
notifyReply_sendsEmailOnlyToUsersWithReplyNotificationsEnabled |
Removed isNotifyOnReply() guard — sent email unconditionally |
DETECTED |
| N5 |
notifyMentions_createsNotificationPerMentionedUser |
Changed NotificationType.MENTION → REPLY in notifyMentions |
DETECTED |
OCR Domain
| ID |
Test |
Mutation |
Result |
| O1 |
getJob_throwsNotFound_whenJobDoesNotExist |
Changed error code OCR_JOB_NOT_FOUND → INTERNAL_ERROR |
DETECTED |
| O2 |
startOcr_throwsBadRequest_whenDocumentIsPlaceholder |
Removed PLACEHOLDER status guard |
DETECTED |
| O3 |
startOcr_throwsServiceUnavailable_whenOcrServiceIsDown |
Removed ocrHealthClient.isHealthy() check |
DETECTED |
| O4 |
startOcr_createsJobAndDispatchesAsync |
Removed ocrAsyncRunner.runSingleDocument(...) call |
DETECTED |
| O5 |
startOcr_updatesScriptType_whenProvided |
Removed documentService.updateScriptType(documentId, scriptTypeOverride) |
DETECTED |
Verdict
All 35 mutations were DETECTED. No tautological tests found. TEST-2 (rewrite phase) has no work to do — the suite is already trustworthy on these critical paths.