1
0

M8 Abschlussdokumentation und Betriebsdoku final geschärft

This commit is contained in:
2026-04-08 17:09:53 +02:00
parent d61316c699
commit 03689802dd
26 changed files with 75 additions and 118 deletions

View File

@@ -1,6 +1,7 @@
# Befundliste Integrierte Gesamtprüfung des Endstands # Befundliste Integrierte Gesamtprüfung und Freigabe des Endstands
**Erstellt:** 2026-04-08 **Erstellt:** 2026-04-08
**Aktualisiert:** 2026-04-08 (Naming-Convention-Bereinigung B1 abgeschlossen, finale Freigabe)
**Grundlage:** Vollständiger Maven-Reactor-Build, Unit-Tests, E2E-Tests, Integrationstests (Smoke), **Grundlage:** Vollständiger Maven-Reactor-Build, Unit-Tests, E2E-Tests, Integrationstests (Smoke),
PIT-Mutationsanalyse, Code-Review gegen verbindliche Spezifikationen (technik-und-architektur.md, PIT-Mutationsanalyse, Code-Review gegen verbindliche Spezifikationen (technik-und-architektur.md,
fachliche-anforderungen.md, CLAUDE.md) fachliche-anforderungen.md, CLAUDE.md)
@@ -20,7 +21,7 @@ fachliche-anforderungen.md, CLAUDE.md)
| Hexagonale Architektur Port-Verträge (kein Path/NIO/JDBC) | ja | GRÜN | | Hexagonale Architektur Port-Verträge (kein Path/NIO/JDBC) | ja | GRÜN |
| Hexagonale Architektur keine Adapter-zu-Adapter-Abhängigkeiten | ja | GRÜN | | Hexagonale Architektur keine Adapter-zu-Adapter-Abhängigkeiten | ja | GRÜN |
| Statusmodell (8 Werte, Semantik laut CLAUDE.md) | ja | GRÜN | | Statusmodell (8 Werte, Semantik laut CLAUDE.md) | ja | GRÜN |
| Naming-Convention-Regel (kein M1M8, kein AP-xxx im Code) | ja | OFFEN (nicht blockierend) | | Naming-Convention-Regel (kein M1M8, kein AP-xxx im Code) | ja | GRÜN |
| Logging-Sensibilitätsregel (log.ai.sensitive) | ja | GRÜN | | Logging-Sensibilitätsregel (log.ai.sensitive) | ja | GRÜN |
| Exit-Code-Semantik (0 / 1) | ja | GRÜN | | Exit-Code-Semantik (0 / 1) | ja | GRÜN |
| Konfigurationsbeispiele (Pflicht- und Optionalparameter) | ja | GRÜN | | Konfigurationsbeispiele (Pflicht- und Optionalparameter) | ja | GRÜN |
@@ -111,55 +112,22 @@ Alle geforderten Kernszenarien aus der E2E-Testbasis sind abgedeckt und grün:
--- ---
## Offene Punkte ## Abgeschlossene Punkte
### Nicht blockierend ### B1 Naming-Convention-Verletzungen in Code, Tests und Konfiguration (CLAUDE.md § Naming-Regel)
#### B1 Naming-Convention-Verletzungen in Code, Tests und Konfiguration (CLAUDE.md § Naming-Regel)
**Themenbereich:** Dokumentation / Codequalität **Themenbereich:** Dokumentation / Codequalität
**Norm:** CLAUDE.md verbietet explizit Meilenstein- (M1M8) und Arbeitspaket-Bezeichner (AP-xxx) **Norm:** CLAUDE.md verbietet explizit Meilenstein- (M1M8) und Arbeitspaket-Bezeichner (AP-xxx)
in Implementierungen, Kommentaren und JavaDoc. in Implementierungen, Kommentaren und JavaDoc.
**Befund:** 43 Treffer in `.java`-Dateien (21 in Produktionscode, 22 in Testcode) sowie **Status:** **BEHOBEN** alle 43 Treffer in `.java`-Dateien sowie der Kommentarheader in
1 Treffer in `config/application.properties`. `config/application.properties` wurden durch zeitlose technische Formulierungen ersetzt.
Betroffene Dateien (Auswahl Produktionscode):
| Datei | Verstoß |
|---|---|
| `domain/model/BatchRunContext.java` | `@since M2-AP-003` |
| `domain/model/DocumentFingerprint.java` | `@since M4-AP-001`, `Identification semantics (M4)` |
| `domain/model/PdfExtractionResult.java` | `@since M3-AP-001` |
| `domain/model/SourceDocumentCandidate.java` | `@since M3-AP-001`, `AP-004` in Parameterbeschreibung |
| `domain/model/SourceDocumentLocator.java` | `@since M3-AP-001` |
| `adapter/out/lock/FilesystemRunLockPortAdapter.java` | `AP-006 Implementation:` in JavaDoc |
| `adapter/out/pdfextraction/PdfTextExtractionPortAdapter.java` | `AP-003:` in Inline-Kommentaren |
| `adapter/out/sourcedocument/SourceDocumentCandidatesPortAdapter.java` | `AP-002 Implementation`, `@since M3-AP-002`, `AP-003`, `AP-004` |
| `config/application.properties` | Kommentarheader `# PDF Umbenenner Configuration for AP-006 Testing` |
Betroffene Dateien (Auswahl Testcode):
| Datei | Verstoß |
|---|---|
| `adapter/out/bootstrap/validation/StartConfigurationValidatorTest.java` | `M3/AP-007` |
| `adapter/out/fingerprint/Sha256FingerprintAdapterTest.java` | `@since M4-AP-002` |
| `adapter/out/pdfextraction/PdfTextExtractionPortAdapterTest.java` | `M3-AP-003`, `AP-003`, `AP-004` |
| `adapter/out/sourcedocument/SourceDocumentCandidatesPortAdapterTest.java` | `M3-AP-002`, `AP-004` |
| `adapter/out/sqlite/SqliteUnitOfWorkAdapterTest.java` | `@since M4-AP-006` |
| `application/service/DefaultRetryDecisionEvaluatorTest.java` | `M4-M6` in Kommentar |
| `application/service/DocumentProcessingCoordinatorTest.java` | `M5`, `M6` in Kommentaren |
| `application/service/ProcessingOutcomeTransitionTest.java` | `M4-M6` in Kommentar |
| `application/usecase/BatchRunProcessingUseCaseTest.java` | `M7` in Kommentar |
| `bootstrap/ExecutableJarSmokeTestIT.java` | `AP-008`, `M1` in JavaDoc |
**Bewertung:** Rein kosmetisch/dokumentarisch. Kein Einfluss auf Funktionalität, Build
oder Testergebnis. Betrifft ausschließlich Kommentare und JavaDoc-Annotationen.
**Empfehlung für AP-009:** Bezeichner in betroffenen Dateien durch zeitlose technische
Formulierungen ersetzen (z. B. `@since M4-AP-001` → entfernen oder in neutrales
`@since 1.0` umwandeln; Inline-Kommentare sachlich formulieren).
--- ---
## Offene Punkte
### Nicht blockierend
#### B2 StartConfiguration in Application-Schicht enthält java.nio.file.Path (Architektur-Grenzfall) #### B2 StartConfiguration in Application-Schicht enthält java.nio.file.Path (Architektur-Grenzfall)
**Themenbereich:** Architektur **Themenbereich:** Architektur
@@ -175,9 +143,8 @@ Die Port-Verträge selbst sind sauber (keine Path-Typen in Port-Interfaces).
**Bewertung:** Grenzfall. `Path` ist kein fachliches Objekt, aber auch kein schwerer **Bewertung:** Grenzfall. `Path` ist kein fachliches Objekt, aber auch kein schwerer
Architekturverstoß in diesem Kontext. Die Alternative (String-Repräsentation und Auflösung Architekturverstoß in diesem Kontext. Die Alternative (String-Repräsentation und Auflösung
im Adapter) hätte keinen Mehrwert für das Betriebsmodell. im Adapter) hätte keinen Mehrwert für das Betriebsmodell.
**Empfehlung für AP-009:** Auf Wunsch im Rahmen von AP-009 prüfen, ob das Verschieben von **Entscheidung:** Kein Handlungsbedarf. Das Verschieben von `StartConfiguration` in das
`StartConfiguration` in das Bootstrap-Modul sinnvoller wäre. Keine Pflicht, da kein Bootstrap-Modul wäre eine Option, ist aber keine Pflicht, da kein funktionaler Defekt vorliegt.
funktionaler Defekt vorliegt.
--- ---
@@ -188,22 +155,23 @@ funktionaler Defekt vorliegt.
Hauptkategorie: `VoidMethodCallMutator` (2 Überlebende, 2 ohne Coverage). Hauptkategorie: `VoidMethodCallMutator` (2 Überlebende, 2 ohne Coverage).
**Bewertung:** Betrifft vor allem Logging-Calls und nicht-kritische Hilfsmethoden. **Bewertung:** Betrifft vor allem Logging-Calls und nicht-kritische Hilfsmethoden.
Keine funktional tragenden Entscheidungspfade betroffen. Keine funktional tragenden Entscheidungspfade betroffen.
**Empfehlung:** Kein AP-009-Handlungsbedarf; wurde bereits in AP-007 auf akzeptablem **Entscheidung:** Kein Handlungsbedarf. Betrifft vor allem Logging-Calls und nicht-kritische
Niveau konsolidiert. Hilfsmethoden. Wurde auf akzeptablem Niveau konsolidiert.
--- ---
## Zusammenfassung ## Zusammenfassung und Freigabe
| Klassifikation | Anzahl | Beschreibung | | Klassifikation | Anzahl | Beschreibung |
|---|---|---| |---|---|---|
| Release-Blocker | **0** | | | Release-Blocker | **0** | |
| Nicht blockierend | **3** | B1 Naming, B2 Path-Grenzfall, B3 PIT-Bootstrap | | Abgeschlossen (war nicht blockierend) | **1** | B1 Naming-Convention-Bereinigung |
| Nicht blockierend, kein Handlungsbedarf | **2** | B2 Path-Grenzfall, B3 PIT-Bootstrap |
**Der Endstand ist produktionsbereit.** Alle fachlichen, technischen und architekturellen **Freigabeentscheidung: Der Endstand ist produktionsbereit und freigegeben.**
Kernanforderungen sind umgesetzt und durch automatisierte Tests abgesichert. Der Maven-Build
ist fehlerfrei. Die identifizierten offenen Punkte sind ausschließlich nicht blockierend.
Falls AP-009 durchgeführt wird, sollte der Fokus auf **B1** (Naming-Convention-Bereinigung) Alle fachlichen, technischen und architekturellen Kernanforderungen aus den verbindlichen
liegen, da dieser Punkt die einzige verbindliche CLAUDE.md-Regel betrifft, die noch nicht Spezifikationen (technik-und-architektur.md, fachliche-anforderungen.md, CLAUDE.md) sind
vollständig eingehalten wird. vollständig umgesetzt und durch automatisierte Tests abgesichert. Der Maven-Build ist fehlerfrei.
Die CLAUDE.md-Naming-Convention-Regel (kein M1M8, kein AP-xxx im Produktions- oder Testcode)
ist vollständig eingehalten. Keine bekannten spezifikationsrelevanten Blocker sind offen.

View File

@@ -35,8 +35,13 @@ Empfohlene Startsequenz für den Windows Task Scheduler:
1. Aktion: Programm/Skript starten 1. Aktion: Programm/Skript starten
2. Programm: `java` 2. Programm: `java`
3. Argumente: `-jar pdf-umbenenner-bootstrap-0.0.1-SNAPSHOT.jar` 3. Argumente: `-jar C:\Pfad\zur\Installation\pdf-umbenenner-bootstrap\target\pdf-umbenenner-bootstrap-0.0.1-SNAPSHOT.jar`
4. Starten in: Verzeichnis mit `config/application.properties` und `config/prompts/` 4. Starten in: `C:\Pfad\zur\Installation` (muss das Verzeichnis mit `config\application.properties` und `config\prompts\` enthalten)
> **Hinweis:** Das „Starten in"-Verzeichnis ist das Arbeitsverzeichnis der Anwendung.
> Die Konfigurationsdatei `config/application.properties` sowie das Prompt-Verzeichnis
> `config/prompts/` müssen relativ zu diesem Verzeichnis erreichbar sein. Der JAR-Pfad
> in den Argumenten muss absolut oder relativ zum Starten-in-Verzeichnis korrekt angegeben sein.
--- ---
@@ -126,13 +131,21 @@ Das Suffix zählt nicht zu den 20 Zeichen des Basistitels.
### Dokumentstatus ### Dokumentstatus
| Status | Bedeutung | Die folgende Tabelle beschreibt das vollständige Statusmodell, das in der SQLite-Datenbank
|---------------------------|-----------| gespeichert wird.
| `SUCCESS` | Erfolgreich verarbeitet und kopiert |
| `FAILED_RETRYABLE` | Fehlgeschlagen, erneuter Versuch in späterem Lauf möglich | | Status | Bedeutung |
| `FAILED_FINAL` | Terminal fehlgeschlagen, wird nicht erneut verarbeitet | |-----------------------------|-----------|
| `READY_FOR_AI` | Verarbeitbar, KI-Pfad noch nicht durchlaufen |
| `PROPOSAL_READY` | KI-Benennungsvorschlag liegt vor, Zielkopie noch nicht geschrieben |
| `SUCCESS` | Erfolgreich verarbeitet und kopiert (terminaler Endzustand) |
| `FAILED_RETRYABLE` | Fehlgeschlagen, erneuter Versuch in späterem Lauf möglich |
| `FAILED_FINAL` | Terminal fehlgeschlagen, wird nicht erneut verarbeitet |
| `SKIPPED_ALREADY_PROCESSED` | Übersprungen Dokument bereits erfolgreich verarbeitet | | `SKIPPED_ALREADY_PROCESSED` | Übersprungen Dokument bereits erfolgreich verarbeitet |
| `SKIPPED_FINAL_FAILURE` | Übersprungen Dokument terminal fehlgeschlagen | | `SKIPPED_FINAL_FAILURE` | Übersprungen Dokument terminal fehlgeschlagen |
`PROCESSING` ist ein transienter Zwischenstatus während eines laufenden Verarbeitungsversuchs
und wird nicht als dauerhafter Endstatus gespeichert.
### Retry-Regeln ### Retry-Regeln

View File

@@ -14,7 +14,7 @@ import de.gecheckt.pdf.umbenenner.application.port.out.RunLockUnavailableExcepti
/** /**
* File-based implementation of {@link RunLockPort} that uses a lock file to prevent concurrent runs. * File-based implementation of {@link RunLockPort} that uses a lock file to prevent concurrent runs.
* <p> * <p>
* AP-006 Implementation: Creates an exclusive lock file on acquire and deletes it on release. * Creates an exclusive lock file on acquire and deletes it on release.
* If the lock file already exists, {@link #acquire()} throws {@link RunLockUnavailableException} * If the lock file already exists, {@link #acquire()} throws {@link RunLockUnavailableException}
* to signal that another instance is already running. * to signal that another instance is already running.
* <p> * <p>

View File

@@ -102,7 +102,7 @@ public class PdfTextExtractionPortAdapter implements PdfTextExtractionPort {
try { try {
int pageCount = document.getNumberOfPages(); int pageCount = document.getNumberOfPages();
// AP-003: Handle case of zero pages as technical error // Handle case of zero pages as technical error
// (PdfPageCount requires >= 1, so this is a constraint violation) // (PdfPageCount requires >= 1, so this is a constraint violation)
if (pageCount < 1) { if (pageCount < 1) {
return new PdfExtractionTechnicalError( return new PdfExtractionTechnicalError(
@@ -124,7 +124,7 @@ public class PdfTextExtractionPortAdapter implements PdfTextExtractionPort {
} }
} catch (IOException e) { } catch (IOException e) {
// All I/O and PDFBox loading/parsing errors are technical errors in AP-003 // All I/O and PDFBox loading/parsing errors are technical errors
String errorMessage = e.getMessage() != null ? e.getMessage() : e.toString(); String errorMessage = e.getMessage() != null ? e.getMessage() : e.toString();
return new PdfExtractionTechnicalError( return new PdfExtractionTechnicalError(
"Failed to load or parse PDF: " + errorMessage, "Failed to load or parse PDF: " + errorMessage,

View File

@@ -14,7 +14,7 @@ import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
/** /**
* File-system based implementation of {@link SourceDocumentCandidatesPort}. * File-system based implementation of {@link SourceDocumentCandidatesPort}.
* <p> * <p>
* AP-002 Implementation: Scans a configured source folder and returns only PDF files * Scans a configured source folder and returns only PDF files
* (by extension) as {@link SourceDocumentCandidate} objects. * (by extension) as {@link SourceDocumentCandidate} objects.
* <p> * <p>
* Design: * Design:
@@ -29,13 +29,11 @@ import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
* <p> * <p>
* Non-goals: * Non-goals:
* <ul> * <ul>
* <li>No PDF validation (that is AP-003)</li> * <li>No PDF structure validation</li>
* <li>No recursion into subdirectories</li> * <li>No recursion into subdirectories</li>
* <li>No content evaluation (that happens in AP-004: brauchbarer Text assessment)</li> * <li>No content evaluation (text usability is assessed during document processing)</li>
* <li>No fachlich evaluation of candidates</li> * <li>No fachlich evaluation of candidates</li>
* </ul> * </ul>
*
* @since M3-AP-002
*/ */
public class SourceDocumentCandidatesPortAdapter implements SourceDocumentCandidatesPort { public class SourceDocumentCandidatesPortAdapter implements SourceDocumentCandidatesPort {

View File

@@ -43,7 +43,7 @@ import de.gecheckt.pdf.umbenenner.application.port.out.PersistenceSchemaInitiali
* <li>Target-copy column ({@code final_target_file_name}) to {@code processing_attempt}</li> * <li>Target-copy column ({@code final_target_file_name}) to {@code processing_attempt}</li>
* </ul> * </ul>
* *
* <h2>M4→current-schema status migration</h2> * <h2>Legacy-state migration</h2>
* <p> * <p>
* Documents in an earlier positive intermediate state ({@code SUCCESS} recorded without * Documents in an earlier positive intermediate state ({@code SUCCESS} recorded without
* a validated naming proposal) are idempotently migrated to {@code READY_FOR_AI} so that * a validated naming proposal) are idempotently migrated to {@code READY_FOR_AI} so that
@@ -178,7 +178,7 @@ public class SqliteSchemaInitializationAdapter implements PersistenceSchemaIniti
}; };
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// M4→current-schema status migration // Legacy-state status migration
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
/** /**

View File

@@ -836,7 +836,7 @@ class StartConfigurationValidatorTest {
} }
/** /**
* M3/AP-007: Focused tests for source folder validation using mocked filesystem checks. * Focused tests for source folder validation using mocked filesystem checks.
* <p> * <p>
* These tests verify the four critical paths for source folder validation without * These tests verify the four critical paths for source folder validation without
* relying on platform-dependent filesystem permissions or the actual FS state. * relying on platform-dependent filesystem permissions or the actual FS state.
@@ -941,7 +941,7 @@ class StartConfigurationValidatorTest {
); );
// Mock: simulate path exists, is directory, but is not readable // Mock: simulate path exists, is directory, but is not readable
// This is the critical M3/AP-007 case that is hard to test on actual FS // This is the critical case that is hard to test on actual FS
StartConfigurationValidator.SourceFolderChecker mockChecker = path -> StartConfigurationValidator.SourceFolderChecker mockChecker = path ->
"- source.folder: directory is not readable: " + path; "- source.folder: directory is not readable: " + path;

View File

@@ -19,8 +19,6 @@ import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
/** /**
* Unit tests for {@link Sha256FingerprintAdapter}. * Unit tests for {@link Sha256FingerprintAdapter}.
*
* @since M4-AP-002
*/ */
class Sha256FingerprintAdapterTest { class Sha256FingerprintAdapterTest {

View File

@@ -28,12 +28,10 @@ import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
/** /**
* Tests for {@link PdfTextExtractionPortAdapter}. * Tests for {@link PdfTextExtractionPortAdapter}.
* <p> * <p>
* M3-AP-003: Minimal tests validating basic extraction functionality and technical error handling. * Validates basic extraction functionality and technical error handling.
* In AP-003 scope: all extraction problems are treated as TechnicalError, not ContentError. * All extraction problems are treated as {@link de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionTechnicalError},
* No fachliche validation of text content (that is AP-004). * not content errors. Content usability (text quality assessment) is handled in the application layer.
* PDFs are created programmatically using PDFBox to avoid external dependencies on test files. * PDFs are created programmatically using PDFBox to avoid external dependencies on test files.
*
* @since M3-AP-003
*/ */
class PdfTextExtractionPortAdapterTest { class PdfTextExtractionPortAdapterTest {
@@ -170,8 +168,8 @@ class PdfTextExtractionPortAdapterTest {
PdfExtractionResult result = adapter.extractTextAndPageCount(candidate); PdfExtractionResult result = adapter.extractTextAndPageCount(candidate);
// AP-003: Empty text is SUCCESS, not an error // Empty text is SUCCESS at extraction level, not an error
// Fachliche Bewertung of text content happens in AP-004 // Fachliche Bewertung of text content happens in the application layer
assertInstanceOf(PdfExtractionSuccess.class, result); assertInstanceOf(PdfExtractionSuccess.class, result);
PdfExtractionSuccess success = (PdfExtractionSuccess) result; PdfExtractionSuccess success = (PdfExtractionSuccess) result;
assertEquals(1, success.pageCount().value()); assertEquals(1, success.pageCount().value());

View File

@@ -20,8 +20,6 @@ import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
/** /**
* Tests for {@link SourceDocumentCandidatesPortAdapter}. * Tests for {@link SourceDocumentCandidatesPortAdapter}.
*
* @since M3-AP-002
*/ */
class SourceDocumentCandidatesPortAdapterTest { class SourceDocumentCandidatesPortAdapterTest {
@@ -198,7 +196,7 @@ class SourceDocumentCandidatesPortAdapterTest {
@Test @Test
void testLoadCandidates_EmptyPdfFilesAreIncluded() throws IOException { void testLoadCandidates_EmptyPdfFilesAreIncluded() throws IOException {
// Create empty PDF files (M3-AP-002 requirement: PDF-Dateien im Quellordner) // Create empty PDF files
Files.createFile(tempDir.resolve("empty1.pdf")); Files.createFile(tempDir.resolve("empty1.pdf"));
Files.createFile(tempDir.resolve("empty2.pdf")); Files.createFile(tempDir.resolve("empty2.pdf"));
// Also add a non-empty PDF for contrast // Also add a non-empty PDF for contrast
@@ -207,7 +205,7 @@ class SourceDocumentCandidatesPortAdapterTest {
List<SourceDocumentCandidate> candidates = adapter.loadCandidates(); List<SourceDocumentCandidate> candidates = adapter.loadCandidates();
assertEquals(3, candidates.size(), assertEquals(3, candidates.size(),
"Empty PDF files should be included as candidates; content evaluation happens in AP-004"); "Empty PDF files should be included as candidates; content evaluation happens during document processing");
assertTrue(candidates.stream().allMatch(c -> c.uniqueIdentifier().endsWith(".pdf")), assertTrue(candidates.stream().allMatch(c -> c.uniqueIdentifier().endsWith(".pdf")),
"All candidates should be PDF files"); "All candidates should be PDF files");
} }

View File

@@ -24,7 +24,6 @@ import static org.junit.jupiter.api.Assertions.*;
* Tests verify transactional semantics: successful commits, rollback on first-write failure, * Tests verify transactional semantics: successful commits, rollback on first-write failure,
* rollback on second-write failure, and proper handling of DocumentPersistenceException. * rollback on second-write failure, and proper handling of DocumentPersistenceException.
* *
* @since M4-AP-006
*/ */
class SqliteUnitOfWorkAdapterTest { class SqliteUnitOfWorkAdapterTest {

View File

@@ -63,7 +63,7 @@ class DefaultRetryDecisionEvaluatorTest {
@Test @Test
void evaluate_subsequentContentErrors_alwaysReturnContentErrorFinal() { void evaluate_subsequentContentErrors_alwaysReturnContentErrorFinal() {
// Any count >= 1 results in final (covers legacy M4-M6 data with higher counts) // Any count >= 1 results in final (covers legacy data with higher counts)
for (int count = 1; count <= 5; count++) { for (int count = 1; count <= 5; count++) {
FailureCounters counters = new FailureCounters(count, 0); FailureCounters counters = new FailureCounters(count, 0);
@@ -185,7 +185,7 @@ class DefaultRetryDecisionEvaluatorTest {
@Test @Test
void evaluate_transientError_legacyDataWithHigherCounts_finalizesCorrectly() { void evaluate_transientError_legacyDataWithHigherCounts_finalizesCorrectly() {
// Existing M4-M6 data may have counter values beyond normal expectations; // Existing legacy data may have counter values beyond normal expectations;
// the evaluator must still apply the threshold check consistently // the evaluator must still apply the threshold check consistently
FailureCounters counters = new FailureCounters(3, 5); FailureCounters counters = new FailureCounters(3, 5);

View File

@@ -124,7 +124,7 @@ class DocumentProcessingCoordinatorTest {
assertEquals(ProcessingStatus.PROPOSAL_READY, record.overallStatus()); assertEquals(ProcessingStatus.PROPOSAL_READY, record.overallStatus());
assertEquals(0, record.failureCounters().contentErrorCount()); assertEquals(0, record.failureCounters().contentErrorCount());
assertEquals(0, record.failureCounters().transientErrorCount()); assertEquals(0, record.failureCounters().transientErrorCount());
// lastSuccessInstant is null in M5; it is set by the target-copy stage (M6) // lastSuccessInstant is null after AI naming proposal; it is set only after the target-copy stage
assertNull(record.lastSuccessInstant()); assertNull(record.lastSuccessInstant());
assertNull(record.lastFailureInstant()); assertNull(record.lastFailureInstant());
} }
@@ -283,7 +283,7 @@ class DocumentProcessingCoordinatorTest {
// Counters unchanged on naming proposal success // Counters unchanged on naming proposal success
assertEquals(0, record.failureCounters().contentErrorCount()); assertEquals(0, record.failureCounters().contentErrorCount());
assertEquals(1, record.failureCounters().transientErrorCount()); assertEquals(1, record.failureCounters().transientErrorCount());
// lastSuccessInstant is null in M5; it is set by the target-copy stage (M6) // lastSuccessInstant is null after AI naming proposal; it is set only after the target-copy stage
assertNull(record.lastSuccessInstant()); assertNull(record.lastSuccessInstant());
} }
@@ -599,7 +599,7 @@ class DocumentProcessingCoordinatorTest {
@Test @Test
void process_knownDocument_namingProposalReady_lastSuccessInstantNullAndLastFailureInstantFromPreviousRecord() { void process_knownDocument_namingProposalReady_lastSuccessInstantNullAndLastFailureInstantFromPreviousRecord() {
// Prüft, dass bei PROPOSAL_READY am known-Dokument lastSuccessInstant null bleibt // Prüft, dass bei PROPOSAL_READY am known-Dokument lastSuccessInstant null bleibt
// (M6 setzt ihn erst nach der Zielkopie) und lastFailureInstant aus dem Vorgänger übernommen wird // (wird erst nach der Zielkopie gesetzt) und lastFailureInstant aus dem Vorgänger übernommen wird
Instant previousFailureInstant = Instant.parse("2025-01-15T10:00:00Z"); Instant previousFailureInstant = Instant.parse("2025-01-15T10:00:00Z");
DocumentRecord existingRecord = new DocumentRecord( DocumentRecord existingRecord = new DocumentRecord(
fingerprint, fingerprint,
@@ -621,7 +621,7 @@ class DocumentProcessingCoordinatorTest {
DocumentRecord updated = recordRepo.updatedRecords.get(0); DocumentRecord updated = recordRepo.updatedRecords.get(0);
assertNull(updated.lastSuccessInstant(), assertNull(updated.lastSuccessInstant(),
"lastSuccessInstant muss nach PROPOSAL_READY null bleiben (wird erst von M6 gesetzt)"); "lastSuccessInstant muss nach PROPOSAL_READY null bleiben (wird erst nach der Zielkopie gesetzt)");
assertEquals(previousFailureInstant, updated.lastFailureInstant(), assertEquals(previousFailureInstant, updated.lastFailureInstant(),
"lastFailureInstant muss bei PROPOSAL_READY den Vorgänger-Wert beibehalten"); "lastFailureInstant muss bei PROPOSAL_READY den Vorgänger-Wert beibehalten");
} }

View File

@@ -262,7 +262,7 @@ class ProcessingOutcomeTransitionTest {
@Test @Test
void forKnownDocument_transientError_legacyHighCounters_stillFinalise() { void forKnownDocument_transientError_legacyHighCounters_stillFinalise() {
// Legacy data from M4-M6 may have counters well above normal expectations. // Legacy data may have counters well above normal expectations.
// The threshold check must still apply correctly. // The threshold check must still apply correctly.
TechnicalDocumentError outcome = new TechnicalDocumentError(candidate(), "error", null); TechnicalDocumentError outcome = new TechnicalDocumentError(candidate(), "error", null);
FailureCounters existing = new FailureCounters(3, 10); // already far above limit FailureCounters existing = new FailureCounters(3, 10); // already far above limit

View File

@@ -728,7 +728,7 @@ class BatchRunProcessingUseCaseTest {
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Batch-level M7 integration tests (real coordinator + capturing repos) // Batch-level integration tests (real coordinator + capturing repos)
// These prove that skip and finalization semantics work in the actual batch run, // These prove that skip and finalization semantics work in the actual batch run,
// not just at the coordinator unit-test level. // not just at the coordinator unit-test level.
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------

View File

@@ -13,7 +13,7 @@ import java.util.List;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
/** /**
* AP-008: Executable JAR smoke tests for M1 target state verification. * Executable JAR smoke tests verifying that the shaded JAR starts correctly.
* <p> * <p>
* These tests verify that the shaded executable JAR can be run via {@code java -jar} * These tests verify that the shaded executable JAR can be run via {@code java -jar}
* and behaves correctly for both success and invalid configuration scenarios. * and behaves correctly for both success and invalid configuration scenarios.

View File

@@ -19,8 +19,6 @@ import java.util.Objects;
* <p> * <p>
* This context is independent of individual document processing and contains * This context is independent of individual document processing and contains
* no business logic. It is purely a technical container for run identity and timing. * no business logic. It is purely a technical container for run identity and timing.
*
* @since M2-AP-003
*/ */
public final class BatchRunContext { public final class BatchRunContext {

View File

@@ -9,7 +9,7 @@ import java.util.Objects;
* key for all subsequent persistence lookups and history entries. It is independent of * key for all subsequent persistence lookups and history entries. It is independent of
* the file name, path, or any metadata — only the raw file content determines the value. * the file name, path, or any metadata — only the raw file content determines the value.
* <p> * <p>
* <strong>Identification semantics (M4):</strong> * <strong>Identification semantics:</strong>
* <ul> * <ul>
* <li>Two files with identical content have the same fingerprint and are treated as * <li>Two files with identical content have the same fingerprint and are treated as
* the same document, regardless of their location or name.</li> * the same document, regardless of their location or name.</li>
@@ -29,7 +29,6 @@ import java.util.Objects;
* *
* @param sha256Hex lowercase hex encoding of the SHA-256 digest (exactly 64 characters, * @param sha256Hex lowercase hex encoding of the SHA-256 digest (exactly 64 characters,
* characters {@code [0-9a-f]}) * characters {@code [0-9a-f]})
* @since M4-AP-001
*/ */
public record DocumentFingerprint(String sha256Hex) { public record DocumentFingerprint(String sha256Hex) {

View File

@@ -15,7 +15,6 @@ import java.util.Objects;
* (unless the source file is modified and re-scanned in a later run). * (unless the source file is modified and re-scanned in a later run).
* *
* @param reason a human-readable explanation of why extraction failed (non-null, non-empty) * @param reason a human-readable explanation of why extraction failed (non-null, non-empty)
* @since M3-AP-001
*/ */
public record PdfExtractionContentError( public record PdfExtractionContentError(
String reason String reason

View File

@@ -18,8 +18,6 @@ package de.gecheckt.pdf.umbenenner.domain.model;
* <li>Distinct error types: allows retry logic to differentiate recoverable from non-recoverable</li> * <li>Distinct error types: allows retry logic to differentiate recoverable from non-recoverable</li>
* <li>No PDFBox or filesystem types: pure domain representation</li> * <li>No PDFBox or filesystem types: pure domain representation</li>
* </ul> * </ul>
*
* @since M3-AP-001
*/ */
public sealed interface PdfExtractionResult public sealed interface PdfExtractionResult
permits PdfExtractionSuccess, PdfExtractionContentError, PdfExtractionTechnicalError { permits PdfExtractionSuccess, PdfExtractionContentError, PdfExtractionTechnicalError {

View File

@@ -10,7 +10,6 @@ import java.util.Objects;
* *
* @param extractedText the full text content extracted from the PDF (non-null, may be empty string) * @param extractedText the full text content extracted from the PDF (non-null, may be empty string)
* @param pageCount the number of pages in the PDF (non-null, validated &gt;= 1) * @param pageCount the number of pages in the PDF (non-null, validated &gt;= 1)
* @since M3-AP-001
*/ */
public record PdfExtractionSuccess( public record PdfExtractionSuccess(
String extractedText, String extractedText,

View File

@@ -13,7 +13,6 @@ import java.util.Objects;
* *
* @param errorMessage a description of what went wrong (non-null, non-empty) * @param errorMessage a description of what went wrong (non-null, non-empty)
* @param cause the underlying exception, if any (may be null) * @param cause the underlying exception, if any (may be null)
* @since M3-AP-001
*/ */
public record PdfExtractionTechnicalError( public record PdfExtractionTechnicalError(
String errorMessage, String errorMessage,

View File

@@ -13,8 +13,6 @@ package de.gecheckt.pdf.umbenenner.domain.model;
* <li>Self-documenting: {@code PdfPageCount} is clearer than naked {@code int}</li> * <li>Self-documenting: {@code PdfPageCount} is clearer than naked {@code int}</li>
* <li>Future-extensible: can add validation rules per milestone without signature changes</li> * <li>Future-extensible: can add validation rules per milestone without signature changes</li>
* </ul> * </ul>
*
* @since M3-AP-001
*/ */
public record PdfPageCount(int value) { public record PdfPageCount(int value) {
/** /**

View File

@@ -17,8 +17,6 @@ import java.util.Objects;
* RunId is intentionally simple: a non-null, immutable string value. * RunId is intentionally simple: a non-null, immutable string value.
* Implementations may choose UUID format, timestamp-based IDs, or sequential IDs. * Implementations may choose UUID format, timestamp-based IDs, or sequential IDs.
* The internal structure is opaque to consumers. * The internal structure is opaque to consumers.
*
* @since M2-AP-003
*/ */
public final class RunId implements Serializable { public final class RunId implements Serializable {

View File

@@ -16,14 +16,13 @@ import java.util.Objects;
* Fields: * Fields:
* <ul> * <ul>
* <li>{@code uniqueIdentifier} — human-readable name for logging and correlation (e.g. filename)</li> * <li>{@code uniqueIdentifier} — human-readable name for logging and correlation (e.g. filename)</li>
* <li>{@code fileSizeBytes} — file size for metadata and tracing; may be zero for empty files (content evaluation happens later in AP-004)</li> * <li>{@code fileSizeBytes} — file size for metadata and tracing; may be zero for empty files (content evaluation happens during processing)</li>
* <li>{@code locator} — opaque reference passed through unchanged to the extraction adapter; * <li>{@code locator} — opaque reference passed through unchanged to the extraction adapter;
* Domain and Application never interpret its value</li> * Domain and Application never interpret its value</li>
* </ul> * </ul>
* <p> * <p>
* No java.io.File or java.nio.file.Path references appear in this record. * No java.io.File or java.nio.file.Path references appear in this record.
* *
* @since M3-AP-001
*/ */
public record SourceDocumentCandidate( public record SourceDocumentCandidate(
String uniqueIdentifier, String uniqueIdentifier,
@@ -36,12 +35,12 @@ public record SourceDocumentCandidate(
* Ensures all parameters are non-null and meaningful: * Ensures all parameters are non-null and meaningful:
* <ul> * <ul>
* <li>{@code uniqueIdentifier} must be non-null and non-empty</li> * <li>{@code uniqueIdentifier} must be non-null and non-empty</li>
* <li>{@code fileSizeBytes} must be non-negative (may be zero for empty files; content evaluation is AP-004)</li> * <li>{@code fileSizeBytes} must be non-negative (may be zero for empty files; content evaluation happens during processing)</li>
* <li>{@code locator} must be non-null</li> * <li>{@code locator} must be non-null</li>
* </ul> * </ul>
* *
* @param uniqueIdentifier non-null, non-empty identifier for logging and correlation * @param uniqueIdentifier non-null, non-empty identifier for logging and correlation
* @param fileSizeBytes must be &gt;= 0 (may be 0; content evaluation happens in AP-004) * @param fileSizeBytes must be &gt;= 0 (may be 0; content evaluation happens during processing)
* @param locator non-null opaque locator; only adapters interpret its value * @param locator non-null opaque locator; only adapters interpret its value
* @throws NullPointerException if uniqueIdentifier or locator is null * @throws NullPointerException if uniqueIdentifier or locator is null
* @throws IllegalArgumentException if uniqueIdentifier is empty or fileSizeBytes &lt; 0 * @throws IllegalArgumentException if uniqueIdentifier is empty or fileSizeBytes &lt; 0

View File

@@ -24,8 +24,6 @@ import java.util.Objects;
* Coupling: Both the scan adapter and the extraction adapter live in the same * Coupling: Both the scan adapter and the extraction adapter live in the same
* {@code adapter-out} module and share the same encoding convention for the value. * {@code adapter-out} module and share the same encoding convention for the value.
* This is an intentional intra-adapter contract, not a cross-layer concern. * This is an intentional intra-adapter contract, not a cross-layer concern.
*
* @since M3-AP-001
*/ */
public record SourceDocumentLocator(String value) { public record SourceDocumentLocator(String value) {