diff --git a/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentService.java b/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentService.java
index 66a56a22..d4715f40 100644
--- a/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentService.java
+++ b/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentService.java
@@ -1008,9 +1008,20 @@ public class DocumentService {
/**
* Lightweight summary lookup for internal use (e.g. journey item append validation).
- * Intentionally skips scope checks and tag-colour resolution — safe only
- * under the current single-tenant model where all authenticated users share
- * the same document scope. Called within a caller-provided transaction.
+ *
+ *
Security contract — read before calling:
+ *
+ * - This method intentionally bypasses per-document scope checks and
+ * tag-colour resolution. It must only be invoked after
+ * {@code @RequirePermission(BLOG_WRITE)} has already been enforced at
+ * the controller layer, guaranteeing the caller is an authenticated
+ * author.
+ * - In {@code JourneyItemService.append()}, it is additionally guarded by the
+ * JOURNEY-type check that fires before this call — so the method is never
+ * reached for STORY-type Geschichten.
+ *
+ * Under the current single-tenant model every authenticated author shares the
+ * same document scope, so skipping per-document scope checks is safe.
*/
public Document findSummaryByIdInternal(UUID id) {
return documentRepository.findById(id)
diff --git a/backend/src/test/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyItemServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyItemServiceTest.java
index 91a51c93..c411a3bb 100644
--- a/backend/src/test/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyItemServiceTest.java
+++ b/backend/src/test/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyItemServiceTest.java
@@ -39,6 +39,7 @@ import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
@@ -235,6 +236,26 @@ class JourneyItemServiceTest {
.isEqualTo(ErrorCode.GESCHICHTE_TYPE_MISMATCH));
}
+ @Test
+ void append_never_calls_findSummaryByIdInternal_when_geschichte_type_is_STORY() {
+ // Arrange: mock geschichteQueryService.findById() to return a STORY-type Geschichte
+ UUID storyId = UUID.randomUUID();
+ Geschichte story = Geschichte.builder()
+ .id(storyId)
+ .type(GeschichteType.STORY)
+ .build();
+ when(geschichteQueryService.findById(storyId)).thenReturn(Optional.of(story));
+
+ // Act + Assert: calling append throws GESCHICHTE_TYPE_MISMATCH
+ JourneyItemCreateDTO dto = new JourneyItemCreateDTO();
+ dto.setDocumentId(UUID.randomUUID());
+ assertThatThrownBy(() -> journeyItemService.append(storyId, dto))
+ .isInstanceOf(DomainException.class);
+
+ // Verify: document service was never touched — type guard fired first
+ verifyNoInteractions(documentService);
+ }
+
@Test
void append_returns404_when_documentId_does_not_exist() {
Geschichte journey = journey(geschichteId);