diff --git a/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/AiInvocationPort.java b/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/AiInvocationPort.java index 1644232..cd3924c 100644 --- a/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/AiInvocationPort.java +++ b/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/AiInvocationPort.java @@ -40,8 +40,6 @@ import de.gecheckt.pdf.umbenenner.domain.model.AiRequestRepresentation; * Completions API or a compatible endpoint. The {@code AiRequestRepresentation} * contains the prompt and document text; the adapter is responsible for formatting * these as needed (e.g., system message + user message in the Chat API). - * - * @since M5 */ public interface AiInvocationPort { diff --git a/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/AiInvocationResult.java b/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/AiInvocationResult.java index 335f75a..c4e6388 100644 --- a/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/AiInvocationResult.java +++ b/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/AiInvocationResult.java @@ -20,8 +20,6 @@ package de.gecheckt.pdf.umbenenner.application.port.out; * was sent and a response was received, but the response content may still be unparseable * or semantically invalid. This is crucial for retry logic: a technical HTTP success * with unparseable JSON is different from a timeout or network error. - * - * @since M5 */ public sealed interface AiInvocationResult permits AiInvocationSuccess, AiInvocationTechnicalFailure { diff --git a/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/AiInvocationSuccess.java b/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/AiInvocationSuccess.java index a0d9d0a..1a63bb4 100644 --- a/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/AiInvocationSuccess.java +++ b/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/AiInvocationSuccess.java @@ -32,8 +32,6 @@ import java.util.Objects; * * @param request the AI request that was sent; never null * @param rawResponse the uninterpreted response body; never null (but may be empty) - * - * @since M5 */ public record AiInvocationSuccess( AiRequestRepresentation request, diff --git a/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/AiInvocationTechnicalFailure.java b/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/AiInvocationTechnicalFailure.java index c288abb..710259b 100644 --- a/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/AiInvocationTechnicalFailure.java +++ b/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/AiInvocationTechnicalFailure.java @@ -32,8 +32,6 @@ import java.util.Objects; * never null * @param failureReason classification of the error type; never null (may be empty) * @param failureMessage human-readable error description; never null (may be empty) - * - * @since M5 */ public record AiInvocationTechnicalFailure( AiRequestRepresentation request, diff --git a/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/PromptLoadingFailure.java b/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/PromptLoadingFailure.java index 62334de..ef468b7 100644 --- a/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/PromptLoadingFailure.java +++ b/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/PromptLoadingFailure.java @@ -22,8 +22,6 @@ import java.util.Objects; * * @param failureReason classification of the failure (non-null, may be empty) * @param failureMessage human-readable failure description (non-null, may be empty) - * - * @since M5 */ public record PromptLoadingFailure( String failureReason, diff --git a/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/PromptLoadingResult.java b/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/PromptLoadingResult.java index fe83d81..41a97fd 100644 --- a/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/PromptLoadingResult.java +++ b/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/PromptLoadingResult.java @@ -11,8 +11,6 @@ package de.gecheckt.pdf.umbenenner.application.port.out; *
- * AI-based naming ports (M5+): + * AI-based naming and invocation ports: *
* Exception types: diff --git a/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/AiErrorClassification.java b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/AiErrorClassification.java index 1a8aac6..5e90ca3 100644 --- a/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/AiErrorClassification.java +++ b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/AiErrorClassification.java @@ -18,7 +18,6 @@ package de.gecheckt.pdf.umbenenner.domain.model; * a later run, while functional errors are subject to the deterministic failure rule * (first occurrence retryable, second occurrence final). * - * @since M5 */ public enum AiErrorClassification { diff --git a/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/AiRawResponse.java b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/AiRawResponse.java index 2c77611..bebb1dd 100644 --- a/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/AiRawResponse.java +++ b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/AiRawResponse.java @@ -29,8 +29,6 @@ import java.util.Objects; * * * @param content the raw response body as a string (non-null, may be empty or malformed) - * - * @since M5 */ public record AiRawResponse(String content) { diff --git a/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/AiRequestRepresentation.java b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/AiRequestRepresentation.java index bcea8ed..57c6009 100644 --- a/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/AiRequestRepresentation.java +++ b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/AiRequestRepresentation.java @@ -45,8 +45,6 @@ import java.util.Objects; * never null (may be empty) * @param sentCharacterCount exact number of characters from documentText that were * sent to the AI; must be >= 0 and <= documentText.length() - * - * @since M5 */ public record AiRequestRepresentation( PromptIdentifier promptIdentifier, diff --git a/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/AiResponseParsingFailure.java b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/AiResponseParsingFailure.java new file mode 100644 index 0000000..9b41ae3 --- /dev/null +++ b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/AiResponseParsingFailure.java @@ -0,0 +1,46 @@ +package de.gecheckt.pdf.umbenenner.domain.model; + +import java.util.Objects; + +/** + * Represents failure to parse an AI response or extract required fields. + *
+ * The response body was received, but either: + *
+ * This is a technical parsing error, distinct from semantic validation failures. + * A response with valid JSON but semantically invalid field values (e.g., overly long title) + * is NOT a parsing failure; it is a parsing success with a functional validation error. + *
+ * Field semantics: + *
+ * Retry semantics: Parsing failures are technical and retryable. + * The document should be retried in a later batch run up to the configured maximum + * for transient errors. + * + * @param failureReason classification of the parsing failure (non-null, may be empty) + * @param failureMessage human-readable error description (non-null, may be empty) + */ +public record AiResponseParsingFailure( + String failureReason, + String failureMessage) implements AiResponseParsingResult { + + /** + * Compact constructor validating mandatory fields. + * + * @throws NullPointerException if any field is null + */ + public AiResponseParsingFailure { + Objects.requireNonNull(failureReason, "failureReason must not be null"); + Objects.requireNonNull(failureMessage, "failureMessage must not be null"); + } +} diff --git a/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/AiResponseParsingResult.java b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/AiResponseParsingResult.java new file mode 100644 index 0000000..7bc40cb --- /dev/null +++ b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/AiResponseParsingResult.java @@ -0,0 +1,25 @@ +package de.gecheckt.pdf.umbenenner.domain.model; + +/** + * Sealed interface representing the outcome of parsing an AI response into its JSON structure. + *
+ * Implementations distinguish between: + *
+ * Usage in the processing pipeline: + *
+ * This distinction is crucial for error classification: an unparseable JSON response + * is a technical error (retryable), while a valid JSON with semantically invalid field + * values is a functional error (deterministic). + */ +public sealed interface AiResponseParsingResult + permits AiResponseParsingSuccess, AiResponseParsingFailure { +} diff --git a/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/AiResponseParsingSuccess.java b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/AiResponseParsingSuccess.java new file mode 100644 index 0000000..c36f57d --- /dev/null +++ b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/AiResponseParsingSuccess.java @@ -0,0 +1,35 @@ +package de.gecheckt.pdf.umbenenner.domain.model; + +import java.util.Objects; + +/** + * Represents successful parsing of an AI response into its JSON structure. + *
+ * The response body was parsed as JSON, the expected fields (title, reasoning, optional date) + * were extracted, and are now available for semantic validation. + *
+ * Field semantics: + *
+ * Next step: Semantic validation of the parsed fields (title length, + * date format, etc.) is performed separately, in the Application layer. This separation + * allows precise error classification: parse failures are technical, validation failures + * are functional/deterministic. + * + * @param response the parsed response structure; never null + */ +public record AiResponseParsingSuccess( + ParsedAiResponse response) implements AiResponseParsingResult { + + /** + * Compact constructor validating the response field. + * + * @throws NullPointerException if response is null + */ + public AiResponseParsingSuccess { + Objects.requireNonNull(response, "response must not be null"); + } +} diff --git a/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/DateSource.java b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/DateSource.java index 049fc8b..7e1989c 100644 --- a/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/DateSource.java +++ b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/DateSource.java @@ -17,7 +17,6 @@ package de.gecheckt.pdf.umbenenner.domain.model; * The source is recorded in the processing attempt history for reproducibility * and operational transparency. * - * @since M5 */ public enum DateSource { diff --git a/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/NamingProposal.java b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/NamingProposal.java index c63e9fd..137100e 100644 --- a/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/NamingProposal.java +++ b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/NamingProposal.java @@ -41,8 +41,6 @@ import java.util.Objects; * @param validatedTitle the title validated per application rules (non-null, non-empty, * max 20 base characters as defined in requirements) * @param aiReasoning the AI's explanation for the proposal (non-null, may be empty) - * - * @since M5 */ public record NamingProposal( LocalDate resolvedDate, diff --git a/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/ParsedAiResponse.java b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/ParsedAiResponse.java new file mode 100644 index 0000000..dad225d --- /dev/null +++ b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/ParsedAiResponse.java @@ -0,0 +1,67 @@ +package de.gecheckt.pdf.umbenenner.domain.model; + +import java.util.Objects; +import java.util.Optional; + +/** + * Represents a successfully parsed AI response with the expected JSON structure. + *
+ * This record captures the three core fields that should be present in the AI's JSON response, + * extracted and structurally validated. The values are NOT yet semantically validated + * (e.g., title length, date format validity), but are known to exist and be parseable as strings. + *
+ * Field semantics: + *
+ * Distinction from other types: + *
+ * Usage: The Application layer receives an {@link AiInvocationSuccess},
+ * parses the raw response body, and produces either a {@code ParsedAiResponse} (if the JSON
+ * structure is sound) or an {@link AiResponseParsingFailure} (if parsing or mandatory field
+ * extraction fails). Later processing steps then validate the parsed values semantically.
+ *
+ * @param title the title string extracted from the JSON; never null
+ * @param reasoning the reasoning/explanation string extracted; never null (may be empty)
+ * @param dateString optional date string (e.g., "2026-02-11"); empty Optional if not present
+ */
+public record ParsedAiResponse(
+ String title,
+ String reasoning,
+ Optional
* The document may transition from this state to {@link #PROPOSAL_READY} on successful
* AI-based naming, or to a failure status if the AI step fails.
- *
- * @since M5
*/
READY_FOR_AI,
@@ -80,16 +78,14 @@ public enum ProcessingStatus {
* (idempotency rule), but may still be processed further by subsequent stages.
* The latest processing attempt with this status holds the authoritative naming proposal
* (resolved date, title, reasoning) for subsequent stages.
- *
- * @since M5
*/
PROPOSAL_READY,
/**
* Document was successfully processed and written to the target location.
*
- * As of M5, this status is reserved for the true end-to-end success after the target copy
- * stage. A document with this status will be skipped in all future batch runs.
+ * This status is reserved for the true end-to-end success after the target copy
+ * stage is complete. A document with this status will be skipped in all future batch runs.
* Status is final and irreversible.
*/
SUCCESS,
diff --git a/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/PromptIdentifier.java b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/PromptIdentifier.java
index 3a7ab90..23d7767 100644
--- a/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/PromptIdentifier.java
+++ b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/PromptIdentifier.java
@@ -24,8 +24,6 @@ import java.util.Objects;
* same identifier across batch runs.
*
* @param identifier the stable, non-null identifier string (typically non-empty)
- *
- * @since M5
*/
public record PromptIdentifier(String identifier) {
diff --git a/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/package-info.java b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/package-info.java
index a6a0571..06a461c 100644
--- a/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/package-info.java
+++ b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/package-info.java
@@ -15,14 +15,16 @@
*
- * AI and naming proposal types (M5+):
+ * AI-based naming and proposal types:
*
* Verifies that all required status values are present and correctly defined
- * for M2, M5, and future milestones.
+ * for the document processing pipeline.
*/
class ProcessingStatusTest {
@Test
void allRequiredStatusValuesExist() {
// Verify all status values required by the architecture are present
- // M2+ statuses
assertNotNull(ProcessingStatus.SUCCESS);
assertNotNull(ProcessingStatus.FAILED_RETRYABLE);
assertNotNull(ProcessingStatus.FAILED_FINAL);
assertNotNull(ProcessingStatus.SKIPPED_ALREADY_PROCESSED);
assertNotNull(ProcessingStatus.SKIPPED_FINAL_FAILURE);
assertNotNull(ProcessingStatus.PROCESSING);
- // M5+ statuses
assertNotNull(ProcessingStatus.READY_FOR_AI);
assertNotNull(ProcessingStatus.PROPOSAL_READY);
}
*
diff --git a/pdf-umbenenner-domain/src/test/java/de/gecheckt/pdf/umbenenner/domain/model/ProcessingStatusTest.java b/pdf-umbenenner-domain/src/test/java/de/gecheckt/pdf/umbenenner/domain/model/ProcessingStatusTest.java
index 599d1b0..7597740 100644
--- a/pdf-umbenenner-domain/src/test/java/de/gecheckt/pdf/umbenenner/domain/model/ProcessingStatusTest.java
+++ b/pdf-umbenenner-domain/src/test/java/de/gecheckt/pdf/umbenenner/domain/model/ProcessingStatusTest.java
@@ -8,21 +8,19 @@ import static org.junit.jupiter.api.Assertions.*;
* Unit tests for {@link ProcessingStatus} enumeration.
*