M8 Abschlussdokumentation und Betriebsdoku final geschärft
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
# Befundliste – Integrierte Gesamtprüfung des Endstands
|
||||
# Befundliste – Integrierte Gesamtprüfung und Freigabe des Endstands
|
||||
|
||||
**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),
|
||||
PIT-Mutationsanalyse, Code-Review gegen verbindliche Spezifikationen (technik-und-architektur.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 – keine Adapter-zu-Adapter-Abhängigkeiten | ja | GRÜN |
|
||||
| Statusmodell (8 Werte, Semantik laut CLAUDE.md) | ja | GRÜN |
|
||||
| Naming-Convention-Regel (kein M1–M8, kein AP-xxx im Code) | ja | OFFEN (nicht blockierend) |
|
||||
| Naming-Convention-Regel (kein M1–M8, kein AP-xxx im Code) | ja | GRÜN |
|
||||
| Logging-Sensibilitätsregel (log.ai.sensitive) | ja | GRÜN |
|
||||
| Exit-Code-Semantik (0 / 1) | 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
|
||||
**Norm:** CLAUDE.md verbietet explizit Meilenstein- (M1–M8) und Arbeitspaket-Bezeichner (AP-xxx)
|
||||
in Implementierungen, Kommentaren und JavaDoc.
|
||||
**Befund:** 43 Treffer in `.java`-Dateien (21 in Produktionscode, 22 in Testcode) sowie
|
||||
1 Treffer in `config/application.properties`.
|
||||
|
||||
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).
|
||||
**Status:** **BEHOBEN** – alle 43 Treffer in `.java`-Dateien sowie der Kommentarheader in
|
||||
`config/application.properties` wurden durch zeitlose technische Formulierungen ersetzt.
|
||||
|
||||
---
|
||||
|
||||
## Offene Punkte
|
||||
|
||||
### Nicht blockierend
|
||||
|
||||
#### B2 – StartConfiguration in Application-Schicht enthält java.nio.file.Path (Architektur-Grenzfall)
|
||||
|
||||
**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
|
||||
Architekturverstoß in diesem Kontext. Die Alternative (String-Repräsentation und Auflösung
|
||||
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
|
||||
`StartConfiguration` in das Bootstrap-Modul sinnvoller wäre. Keine Pflicht, da kein
|
||||
funktionaler Defekt vorliegt.
|
||||
**Entscheidung:** Kein Handlungsbedarf. Das Verschieben von `StartConfiguration` in das
|
||||
Bootstrap-Modul wäre eine Option, ist aber keine Pflicht, da kein funktionaler Defekt vorliegt.
|
||||
|
||||
---
|
||||
|
||||
@@ -188,22 +155,23 @@ funktionaler Defekt vorliegt.
|
||||
Hauptkategorie: `VoidMethodCallMutator` (2 Überlebende, 2 ohne Coverage).
|
||||
**Bewertung:** Betrifft vor allem Logging-Calls und nicht-kritische Hilfsmethoden.
|
||||
Keine funktional tragenden Entscheidungspfade betroffen.
|
||||
**Empfehlung:** Kein AP-009-Handlungsbedarf; wurde bereits in AP-007 auf akzeptablem
|
||||
Niveau konsolidiert.
|
||||
**Entscheidung:** Kein Handlungsbedarf. Betrifft vor allem Logging-Calls und nicht-kritische
|
||||
Hilfsmethoden. Wurde auf akzeptablem Niveau konsolidiert.
|
||||
|
||||
---
|
||||
|
||||
## Zusammenfassung
|
||||
## Zusammenfassung und Freigabe
|
||||
|
||||
| Klassifikation | Anzahl | Beschreibung |
|
||||
|---|---|---|
|
||||
| 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
|
||||
Kernanforderungen sind umgesetzt und durch automatisierte Tests abgesichert. Der Maven-Build
|
||||
ist fehlerfrei. Die identifizierten offenen Punkte sind ausschließlich nicht blockierend.
|
||||
**Freigabeentscheidung: Der Endstand ist produktionsbereit und freigegeben.**
|
||||
|
||||
Falls AP-009 durchgeführt wird, sollte der Fokus auf **B1** (Naming-Convention-Bereinigung)
|
||||
liegen, da dieser Punkt die einzige verbindliche CLAUDE.md-Regel betrifft, die noch nicht
|
||||
vollständig eingehalten wird.
|
||||
Alle fachlichen, technischen und architekturellen Kernanforderungen aus den verbindlichen
|
||||
Spezifikationen (technik-und-architektur.md, fachliche-anforderungen.md, CLAUDE.md) sind
|
||||
vollständig umgesetzt und durch automatisierte Tests abgesichert. Der Maven-Build ist fehlerfrei.
|
||||
Die CLAUDE.md-Naming-Convention-Regel (kein M1–M8, kein AP-xxx im Produktions- oder Testcode)
|
||||
ist vollständig eingehalten. Keine bekannten spezifikationsrelevanten Blocker sind offen.
|
||||
|
||||
@@ -35,8 +35,13 @@ Empfohlene Startsequenz für den Windows Task Scheduler:
|
||||
|
||||
1. Aktion: Programm/Skript starten
|
||||
2. Programm: `java`
|
||||
3. Argumente: `-jar pdf-umbenenner-bootstrap-0.0.1-SNAPSHOT.jar`
|
||||
4. Starten in: Verzeichnis mit `config/application.properties` und `config/prompts/`
|
||||
3. Argumente: `-jar C:\Pfad\zur\Installation\pdf-umbenenner-bootstrap\target\pdf-umbenenner-bootstrap-0.0.1-SNAPSHOT.jar`
|
||||
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,14 +131,22 @@ Das Suffix zählt nicht zu den 20 Zeichen des Basistitels.
|
||||
|
||||
### Dokumentstatus
|
||||
|
||||
Die folgende Tabelle beschreibt das vollständige Statusmodell, das in der SQLite-Datenbank
|
||||
gespeichert wird.
|
||||
|
||||
| Status | Bedeutung |
|
||||
|---------------------------|-----------|
|
||||
| `SUCCESS` | Erfolgreich verarbeitet und kopiert |
|
||||
|-----------------------------|-----------|
|
||||
| `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_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
|
||||
|
||||
**Deterministische Inhaltsfehler** (z. B. kein extrahierbarer Text, Seitenlimit überschritten,
|
||||
|
||||
@@ -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.
|
||||
* <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}
|
||||
* to signal that another instance is already running.
|
||||
* <p>
|
||||
|
||||
@@ -102,7 +102,7 @@ public class PdfTextExtractionPortAdapter implements PdfTextExtractionPort {
|
||||
try {
|
||||
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)
|
||||
if (pageCount < 1) {
|
||||
return new PdfExtractionTechnicalError(
|
||||
@@ -124,7 +124,7 @@ public class PdfTextExtractionPortAdapter implements PdfTextExtractionPort {
|
||||
}
|
||||
|
||||
} 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();
|
||||
return new PdfExtractionTechnicalError(
|
||||
"Failed to load or parse PDF: " + errorMessage,
|
||||
|
||||
@@ -14,7 +14,7 @@ import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
|
||||
/**
|
||||
* File-system based implementation of {@link SourceDocumentCandidatesPort}.
|
||||
* <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.
|
||||
* <p>
|
||||
* Design:
|
||||
@@ -29,13 +29,11 @@ import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
|
||||
* <p>
|
||||
* Non-goals:
|
||||
* <ul>
|
||||
* <li>No PDF validation (that is AP-003)</li>
|
||||
* <li>No PDF structure validation</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>
|
||||
* </ul>
|
||||
*
|
||||
* @since M3-AP-002
|
||||
*/
|
||||
public class SourceDocumentCandidatesPortAdapter implements SourceDocumentCandidatesPort {
|
||||
|
||||
|
||||
@@ -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>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>M4→current-schema status migration</h2>
|
||||
* <h2>Legacy-state migration</h2>
|
||||
* <p>
|
||||
* 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
|
||||
@@ -178,7 +178,7 @@ public class SqliteSchemaInitializationAdapter implements PersistenceSchemaIniti
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// M4→current-schema status migration
|
||||
// Legacy-state status migration
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
|
||||
@@ -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>
|
||||
* These tests verify the four critical paths for source folder validation without
|
||||
* 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
|
||||
// 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 ->
|
||||
"- source.folder: directory is not readable: " + path;
|
||||
|
||||
|
||||
@@ -19,8 +19,6 @@ import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link Sha256FingerprintAdapter}.
|
||||
*
|
||||
* @since M4-AP-002
|
||||
*/
|
||||
class Sha256FingerprintAdapterTest {
|
||||
|
||||
|
||||
@@ -28,12 +28,10 @@ import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
|
||||
/**
|
||||
* Tests for {@link PdfTextExtractionPortAdapter}.
|
||||
* <p>
|
||||
* M3-AP-003: Minimal tests validating basic extraction functionality and technical error handling.
|
||||
* In AP-003 scope: all extraction problems are treated as TechnicalError, not ContentError.
|
||||
* No fachliche validation of text content (that is AP-004).
|
||||
* Validates basic extraction functionality and technical error handling.
|
||||
* All extraction problems are treated as {@link de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionTechnicalError},
|
||||
* 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.
|
||||
*
|
||||
* @since M3-AP-003
|
||||
*/
|
||||
class PdfTextExtractionPortAdapterTest {
|
||||
|
||||
@@ -170,8 +168,8 @@ class PdfTextExtractionPortAdapterTest {
|
||||
|
||||
PdfExtractionResult result = adapter.extractTextAndPageCount(candidate);
|
||||
|
||||
// AP-003: Empty text is SUCCESS, not an error
|
||||
// Fachliche Bewertung of text content happens in AP-004
|
||||
// Empty text is SUCCESS at extraction level, not an error
|
||||
// Fachliche Bewertung of text content happens in the application layer
|
||||
assertInstanceOf(PdfExtractionSuccess.class, result);
|
||||
PdfExtractionSuccess success = (PdfExtractionSuccess) result;
|
||||
assertEquals(1, success.pageCount().value());
|
||||
|
||||
@@ -20,8 +20,6 @@ import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
|
||||
|
||||
/**
|
||||
* Tests for {@link SourceDocumentCandidatesPortAdapter}.
|
||||
*
|
||||
* @since M3-AP-002
|
||||
*/
|
||||
class SourceDocumentCandidatesPortAdapterTest {
|
||||
|
||||
@@ -198,7 +196,7 @@ class SourceDocumentCandidatesPortAdapterTest {
|
||||
|
||||
@Test
|
||||
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("empty2.pdf"));
|
||||
// Also add a non-empty PDF for contrast
|
||||
@@ -207,7 +205,7 @@ class SourceDocumentCandidatesPortAdapterTest {
|
||||
List<SourceDocumentCandidate> candidates = adapter.loadCandidates();
|
||||
|
||||
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")),
|
||||
"All candidates should be PDF files");
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
* Tests verify transactional semantics: successful commits, rollback on first-write failure,
|
||||
* rollback on second-write failure, and proper handling of DocumentPersistenceException.
|
||||
*
|
||||
* @since M4-AP-006
|
||||
*/
|
||||
class SqliteUnitOfWorkAdapterTest {
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ class DefaultRetryDecisionEvaluatorTest {
|
||||
|
||||
@Test
|
||||
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++) {
|
||||
FailureCounters counters = new FailureCounters(count, 0);
|
||||
|
||||
@@ -185,7 +185,7 @@ class DefaultRetryDecisionEvaluatorTest {
|
||||
|
||||
@Test
|
||||
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
|
||||
FailureCounters counters = new FailureCounters(3, 5);
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ class DocumentProcessingCoordinatorTest {
|
||||
assertEquals(ProcessingStatus.PROPOSAL_READY, record.overallStatus());
|
||||
assertEquals(0, record.failureCounters().contentErrorCount());
|
||||
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.lastFailureInstant());
|
||||
}
|
||||
@@ -283,7 +283,7 @@ class DocumentProcessingCoordinatorTest {
|
||||
// Counters unchanged on naming proposal success
|
||||
assertEquals(0, record.failureCounters().contentErrorCount());
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -599,7 +599,7 @@ class DocumentProcessingCoordinatorTest {
|
||||
@Test
|
||||
void process_knownDocument_namingProposalReady_lastSuccessInstantNullAndLastFailureInstantFromPreviousRecord() {
|
||||
// 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");
|
||||
DocumentRecord existingRecord = new DocumentRecord(
|
||||
fingerprint,
|
||||
@@ -621,7 +621,7 @@ class DocumentProcessingCoordinatorTest {
|
||||
|
||||
DocumentRecord updated = recordRepo.updatedRecords.get(0);
|
||||
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(),
|
||||
"lastFailureInstant muss bei PROPOSAL_READY den Vorgänger-Wert beibehalten");
|
||||
}
|
||||
|
||||
@@ -262,7 +262,7 @@ class ProcessingOutcomeTransitionTest {
|
||||
|
||||
@Test
|
||||
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.
|
||||
TechnicalDocumentError outcome = new TechnicalDocumentError(candidate(), "error", null);
|
||||
FailureCounters existing = new FailureCounters(3, 10); // already far above limit
|
||||
|
||||
@@ -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,
|
||||
// not just at the coordinator unit-test level.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -13,7 +13,7 @@ import java.util.List;
|
||||
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>
|
||||
* 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.
|
||||
|
||||
@@ -19,8 +19,6 @@ import java.util.Objects;
|
||||
* <p>
|
||||
* This context is independent of individual document processing and contains
|
||||
* no business logic. It is purely a technical container for run identity and timing.
|
||||
*
|
||||
* @since M2-AP-003
|
||||
*/
|
||||
public final class BatchRunContext {
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import java.util.Objects;
|
||||
* 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.
|
||||
* <p>
|
||||
* <strong>Identification semantics (M4):</strong>
|
||||
* <strong>Identification semantics:</strong>
|
||||
* <ul>
|
||||
* <li>Two files with identical content have the same fingerprint and are treated as
|
||||
* 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,
|
||||
* characters {@code [0-9a-f]})
|
||||
* @since M4-AP-001
|
||||
*/
|
||||
public record DocumentFingerprint(String sha256Hex) {
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ import java.util.Objects;
|
||||
* (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)
|
||||
* @since M3-AP-001
|
||||
*/
|
||||
public record PdfExtractionContentError(
|
||||
String reason
|
||||
|
||||
@@ -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>No PDFBox or filesystem types: pure domain representation</li>
|
||||
* </ul>
|
||||
*
|
||||
* @since M3-AP-001
|
||||
*/
|
||||
public sealed interface PdfExtractionResult
|
||||
permits PdfExtractionSuccess, PdfExtractionContentError, PdfExtractionTechnicalError {
|
||||
|
||||
@@ -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 pageCount the number of pages in the PDF (non-null, validated >= 1)
|
||||
* @since M3-AP-001
|
||||
*/
|
||||
public record PdfExtractionSuccess(
|
||||
String extractedText,
|
||||
|
||||
@@ -13,7 +13,6 @@ import java.util.Objects;
|
||||
*
|
||||
* @param errorMessage a description of what went wrong (non-null, non-empty)
|
||||
* @param cause the underlying exception, if any (may be null)
|
||||
* @since M3-AP-001
|
||||
*/
|
||||
public record PdfExtractionTechnicalError(
|
||||
String errorMessage,
|
||||
|
||||
@@ -13,8 +13,6 @@ package de.gecheckt.pdf.umbenenner.domain.model;
|
||||
* <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>
|
||||
* </ul>
|
||||
*
|
||||
* @since M3-AP-001
|
||||
*/
|
||||
public record PdfPageCount(int value) {
|
||||
/**
|
||||
|
||||
@@ -17,8 +17,6 @@ import java.util.Objects;
|
||||
* RunId is intentionally simple: a non-null, immutable string value.
|
||||
* Implementations may choose UUID format, timestamp-based IDs, or sequential IDs.
|
||||
* The internal structure is opaque to consumers.
|
||||
*
|
||||
* @since M2-AP-003
|
||||
*/
|
||||
public final class RunId implements Serializable {
|
||||
|
||||
|
||||
@@ -16,14 +16,13 @@ import java.util.Objects;
|
||||
* Fields:
|
||||
* <ul>
|
||||
* <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;
|
||||
* Domain and Application never interpret its value</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* No java.io.File or java.nio.file.Path references appear in this record.
|
||||
*
|
||||
* @since M3-AP-001
|
||||
*/
|
||||
public record SourceDocumentCandidate(
|
||||
String uniqueIdentifier,
|
||||
@@ -36,12 +35,12 @@ public record SourceDocumentCandidate(
|
||||
* Ensures all parameters are non-null and meaningful:
|
||||
* <ul>
|
||||
* <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>
|
||||
* </ul>
|
||||
*
|
||||
* @param uniqueIdentifier non-null, non-empty identifier for logging and correlation
|
||||
* @param fileSizeBytes must be >= 0 (may be 0; content evaluation happens in AP-004)
|
||||
* @param fileSizeBytes must be >= 0 (may be 0; content evaluation happens during processing)
|
||||
* @param locator non-null opaque locator; only adapters interpret its value
|
||||
* @throws NullPointerException if uniqueIdentifier or locator is null
|
||||
* @throws IllegalArgumentException if uniqueIdentifier is empty or fileSizeBytes < 0
|
||||
|
||||
@@ -24,8 +24,6 @@ import java.util.Objects;
|
||||
* 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.
|
||||
* This is an intentional intra-adapter contract, not a cross-layer concern.
|
||||
*
|
||||
* @since M3-AP-001
|
||||
*/
|
||||
public record SourceDocumentLocator(String value) {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user