diff --git a/pdf-umbenenner-adapter-out/src/main/java/de/gecheckt/pdf/umbenenner/adapter/out/sqlite/SqliteDocumentRecordRepositoryAdapter.java b/pdf-umbenenner-adapter-out/src/main/java/de/gecheckt/pdf/umbenenner/adapter/out/sqlite/SqliteDocumentRecordRepositoryAdapter.java index aa7c108..d99a262 100644 --- a/pdf-umbenenner-adapter-out/src/main/java/de/gecheckt/pdf/umbenenner/adapter/out/sqlite/SqliteDocumentRecordRepositoryAdapter.java +++ b/pdf-umbenenner-adapter-out/src/main/java/de/gecheckt/pdf/umbenenner/adapter/out/sqlite/SqliteDocumentRecordRepositoryAdapter.java @@ -103,7 +103,8 @@ public class SqliteDocumentRecordRepositoryAdapter implements DocumentRecordRepo return switch (record.overallStatus()) { case SUCCESS -> new DocumentTerminalSuccess(record); case FAILED_FINAL -> new DocumentTerminalFinalFailure(record); - case PROCESSING, FAILED_RETRYABLE, SKIPPED_ALREADY_PROCESSED, SKIPPED_FINAL_FAILURE -> + case READY_FOR_AI, PROPOSAL_READY, PROCESSING, FAILED_RETRYABLE, + SKIPPED_ALREADY_PROCESSED, SKIPPED_FINAL_FAILURE -> new DocumentKnownProcessable(record); }; } else { 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 new file mode 100644 index 0000000..1644232 --- /dev/null +++ b/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/AiInvocationPort.java @@ -0,0 +1,77 @@ +package de.gecheckt.pdf.umbenenner.application.port.out; + +import de.gecheckt.pdf.umbenenner.domain.model.AiRequestRepresentation; + +/** + * Outbound port for invoking an AI service over an OpenAI-compatible HTTP boundary. + *
+ * This interface abstracts AI service communication, allowing the Application layer + * to orchestrate AI-based naming without knowing about HTTP, authentication, or + * provider-specific details. + *
+ * Design principles: + *
+ * Adapter responsibilities: + *
+ * Non-goals of this port: + *
+ * OpenAI compatibility: The adapter must support the OpenAI Chat + * 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 { + + /** + * Invokes an AI service with the given request representation. + *
+ * This method sends a request to the configured AI endpoint and returns the result. + * The request contains both the prompt and the document text, deterministically + * composed by the Application layer. + *
+ * Outcome distinction: + *
+ * Implementations allow the Application layer to distinguish between: + *
+ * Permitted implementations: + *
+ * Critical distinction: A successful invocation means the HTTP request + * 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 new file mode 100644 index 0000000..a0d9d0a --- /dev/null +++ b/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/AiInvocationSuccess.java @@ -0,0 +1,51 @@ +package de.gecheckt.pdf.umbenenner.application.port.out; + +import de.gecheckt.pdf.umbenenner.domain.model.AiRawResponse; +import de.gecheckt.pdf.umbenenner.domain.model.AiRequestRepresentation; +import java.util.Objects; + +/** + * Represents successful HTTP communication with an AI service. + *
+ * The HTTP request was sent and a response body was received. This indicates + * technical success of the communication, but does NOT guarantee that the response + * content is valid, parseable, or functionally usable. + *
+ * Field semantics: + *
+ * The Application layer is responsible for: + *
+ * Persistence: Both request and response are stored in the + * processing attempt history for debugging and audit. + * + * @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, + AiRawResponse rawResponse) implements AiInvocationResult { + + /** + * Compact constructor validating mandatory fields. + * + * @throws NullPointerException if either field is null + */ + public AiInvocationSuccess { + Objects.requireNonNull(request, "request must not be null"); + Objects.requireNonNull(rawResponse, "rawResponse must not be null"); + } +} 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 new file mode 100644 index 0000000..c288abb --- /dev/null +++ b/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/AiInvocationTechnicalFailure.java @@ -0,0 +1,53 @@ +package de.gecheckt.pdf.umbenenner.application.port.out; + +import de.gecheckt.pdf.umbenenner.domain.model.AiRequestRepresentation; +import java.util.Objects; + +/** + * Represents a technical failure during AI service invocation. + *
+ * The HTTP request could not be sent, or no valid response body was received. + * This covers network errors, timeouts, endpoint unreachability, connection failures, + * and other infrastructure-level problems. + *
+ * Field semantics: + *
+ * Retry semantics: Technical failures are retryable. The Application + * layer will record this as a transient error, and the document may be retried in + * a later batch run up to the configured maximum transient-error count. + *
+ * Distinction from functional errors: A 200 OK response with an + * invalid JSON body is NOT a technical failure; it's an invocation success that + * contains a functional error. Only communication/transport errors are classified here. + * + * @param request the request that was attempted (may not have been successfully sent); + * 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, + String failureReason, + String failureMessage) implements AiInvocationResult { + + /** + * Compact constructor validating mandatory fields. + * + * @throws NullPointerException if any field is null + */ + public AiInvocationTechnicalFailure { + Objects.requireNonNull(request, "request must not be null"); + Objects.requireNonNull(failureReason, "failureReason must not be null"); + Objects.requireNonNull(failureMessage, "failureMessage must not be null"); + } +} 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 new file mode 100644 index 0000000..62334de --- /dev/null +++ b/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/PromptLoadingFailure.java @@ -0,0 +1,41 @@ +package de.gecheckt.pdf.umbenenner.application.port.out; + +import java.util.Objects; + +/** + * Represents failure to load an external prompt template. + *
+ * The prompt could not be obtained from the configured external source, + * or the loaded content was technically invalid (e.g., empty after trimming). + *
+ * Field semantics: + *
+ * This is a technical failure, not a validation error, and typically prevents + * the batch run from proceeding further (may lead to a {@code PROCESSING} status + * treated as {@code FAILED_RETRYABLE}). + * + * @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, + String failureMessage) implements PromptLoadingResult { + + /** + * Compact constructor validating mandatory fields. + * + * @throws NullPointerException if either field is null + */ + public PromptLoadingFailure { + Objects.requireNonNull(failureReason, "failureReason must not be null"); + Objects.requireNonNull(failureMessage, "failureMessage must not be null"); + } +} 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 new file mode 100644 index 0000000..fe83d81 --- /dev/null +++ b/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/PromptLoadingResult.java @@ -0,0 +1,19 @@ +package de.gecheckt.pdf.umbenenner.application.port.out; + +/** + * Sealed interface representing the outcome of loading an external prompt template. + *
+ * Implementations allow the Application layer to distinguish between a successful + * prompt load and various failure scenarios without using exceptions. + *
+ * Permitted implementations: + *
+ * The prompt content and a stable identifier for the prompt have both been + * successfully obtained from the configured external source. + *
+ * Field semantics: + *
+ * The identifier is crucial for historical traceability: each processing attempt + * records which prompt was used, allowing later investigation of why a particular + * decision was made. + * + * @param promptIdentifier stable identifier for this prompt version; never null + * @param promptContent the prompt template text; never null + * + * @since M5 + */ +public record PromptLoadingSuccess( + PromptIdentifier promptIdentifier, + String promptContent) implements PromptLoadingResult { + + /** + * Compact constructor validating mandatory fields. + * + * @throws NullPointerException if either field is null + */ + public PromptLoadingSuccess { + Objects.requireNonNull(promptIdentifier, "promptIdentifier must not be null"); + Objects.requireNonNull(promptContent, "promptContent must not be null"); + } +} diff --git a/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/PromptPort.java b/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/PromptPort.java new file mode 100644 index 0000000..4d21d44 --- /dev/null +++ b/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/PromptPort.java @@ -0,0 +1,58 @@ +package de.gecheckt.pdf.umbenenner.application.port.out; + +/** + * Outbound port for loading external prompt templates. + *
+ * This interface abstracts the loading of prompt content from external sources + * (files, resources, databases, etc.), allowing the Application layer to remain + * independent of how or where prompts are stored. + *
+ * Design principles: + *
+ * Adapter responsibilities: + *
+ * Non-goals of this port: + *
+ * This method is called once per batch run to obtain the current prompt. + * The prompt content and its stable identifier are returned together. + *
+ * If loading fails for any reason (file not found, I/O error, content validation), + * a {@link PromptLoadingFailure} is returned rather than throwing an exception. + * + * @return a {@link PromptLoadingResult} encoding either: + *
+ * AI-based naming ports (M5+): + *
* Persistence and fingerprinting 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 new file mode 100644 index 0000000..1a8aac6 --- /dev/null +++ b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/AiErrorClassification.java @@ -0,0 +1,47 @@ +package de.gecheckt.pdf.umbenenner.domain.model; + +/** + * Classification of AI-related errors into technical vs. functional categories. + *
+ * This enumeration distinguishes between two fundamental error types that occur + * during AI-based naming proposal generation: + *
+ * The classification determines retry behavior: technical errors may be retried in + * a later run, while functional errors are subject to the deterministic failure rule + * (first occurrence retryable, second occurrence final). + * + * @since M5 + */ +public enum AiErrorClassification { + + /** + * A technical infrastructure or communication failure occurred. + *
+ * Examples: API endpoint not reachable, HTTP timeout, malformed response structure, + * missing mandatory fields in otherwise-parseable JSON, network error. + *
+ * These errors are typically transient and may be resolved by retry in a later + * batch run. The failure is recorded against the transient-error counter. + */ + TECHNICAL, + + /** + * A functional or content validation error occurred. + *
+ * Examples: invalid or generic title (e.g., "Dokument"), unparseable date string, + * AI response violates documented rules (e.g., title contains prohibited characters). + *
+ * These errors are deterministic and reflect issues with the AI-generated content + * itself or the document's content quality. The failure is recorded against the + * content-error counter, subject to the deterministic retry rule. + */ + FUNCTIONAL +} 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 new file mode 100644 index 0000000..2c77611 --- /dev/null +++ b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/AiRawResponse.java @@ -0,0 +1,45 @@ +package de.gecheckt.pdf.umbenenner.domain.model; + +import java.util.Objects; + +/** + * Unvalidated, uninterpreted raw response body from an AI service. + *
+ * This record holds the exact bytes or string returned by the AI HTTP endpoint, + * before any parsing, validation, or business-logic processing. It is used to: + *
+ * Persistance: The raw response is stored in SQLite history for + * traceability and future debugging. It may contain the full JSON structure or + * formatted text, depending on the AI service. + *
+ * Example: + *
+ * {@code
+ * AiRawResponse response = new AiRawResponse(
+ * "{\"date\": \"2026-03-05\", \"title\": \"Stromabrechnung\", \"reasoning\": \"...\"}"
+ * );
+ * }
+ *
+ *
+ * @param content the raw response body as a string (non-null, may be empty or malformed)
+ *
+ * @since M5
+ */
+public record AiRawResponse(String content) {
+
+ /**
+ * Compact constructor validating that content is not null.
+ *
+ * @throws NullPointerException if {@code content} is null
+ */
+ public AiRawResponse {
+ Objects.requireNonNull(content, "content must not be null");
+ }
+}
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
new file mode 100644
index 0000000..bcea8ed
--- /dev/null
+++ b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/AiRequestRepresentation.java
@@ -0,0 +1,73 @@
+package de.gecheckt.pdf.umbenenner.domain.model;
+
+import java.util.Objects;
+
+/**
+ * Deterministic, complete representation of the request sent to an AI service.
+ * + * This record captures the exact prompt, text, and configuration that were sent + * to the AI in a single request, allowing for reproducibility and debugging. + *
+ * Construction: The Application layer constructs this representation + * deterministically from: + *
+ * Field semantics: + *
+ * Persistence: Both prompt identifier and sent character count + * are recorded in the processing attempt history for traceability. + *
+ * Not included: + *
+ * Each enum constant represents a specific origin or determination method for the date + * used in a naming proposal. The source is recorded for traceability. + *
+ * Semantics: + *
+ * The source is recorded in the processing attempt history for reproducibility + * and operational transparency. + * + * @since M5 + */ +public enum DateSource { + + /** + * The date was provided by the AI in its JSON response. + *
+ * The AI explicitly supplied a {@code date} field in valid {@code YYYY-MM-DD} format. + */ + AI_PROVIDED, + + /** + * The date is the current system date used as fallback. + *
+ * The AI either omitted the {@code date} field or provided no usable date. + * The application set the fallback to the current date from {@code ClockPort}. + */ + FALLBACK_CURRENT +} 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 new file mode 100644 index 0000000..c63e9fd --- /dev/null +++ b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/NamingProposal.java @@ -0,0 +1,68 @@ +package de.gecheckt.pdf.umbenenner.domain.model; + +import java.time.LocalDate; +import java.util.Objects; + +/** + * A validated naming proposal derived from AI analysis of a document. + *
+ * This record represents the core results of the AI-based naming stage: + * a proposed date, a proposed title, and the AI's reasoning. All three fields + * have been validated according to application rules at the time of creation. + *
+ * Field semantics: + *
+ * Not included in this proposal: + *
+ * Persistence: The naming proposal is persistently stored as part + * of the processing attempt history for reproducibility and audit. + * + * @param resolvedDate the effective date (never null); derived from AI or fallback + * @param dateSource origin of the date ({@link DateSource#AI_PROVIDED} or + * {@link DateSource#FALLBACK_CURRENT}); never null + * @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, + DateSource dateSource, + String validatedTitle, + String aiReasoning) { + + /** + * Compact constructor validating all mandatory fields. + * + * @throws NullPointerException if any field is null + * @throws IllegalArgumentException if validatedTitle is empty + */ + public NamingProposal { + Objects.requireNonNull(resolvedDate, "resolvedDate must not be null"); + Objects.requireNonNull(dateSource, "dateSource must not be null"); + Objects.requireNonNull(validatedTitle, "validatedTitle must not be null"); + if (validatedTitle.isEmpty()) { + throw new IllegalArgumentException("validatedTitle must not be empty"); + } + Objects.requireNonNull(aiReasoning, "aiReasoning must not be null"); + } +} diff --git a/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/ProcessingStatus.java b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/ProcessingStatus.java index c45eec4..1b6b128 100644 --- a/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/ProcessingStatus.java +++ b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/ProcessingStatus.java @@ -9,7 +9,15 @@ package de.gecheckt.pdf.umbenenner.domain.model; *
* Overall-status semantics (master record): *
* Attempt-status semantics (attempt history): *
+ * This is a non-terminal intermediate state. The document is ready to be processed + * by AI-based naming in the next stage. A document with this status may be reprocessed + * in later runs, and will be updated to a subsequent status (e.g., {@link #PROPOSAL_READY} + * or failure) based on the outcome of the AI naming step. + *
+ * 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, + + /** + * A valid, persistent AI-based naming proposal has been generated and stored. + *
+ * This is a non-terminal intermediate state. The document is complete for its current + * processing stage, but will be processed further in the next stage (target copy, + * final filename generation with duplicate-suffix handling, and final success). + *
+ * A document with this status will not be reprocessed by the AI step in future runs + * (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. *
- * A document with this status will be skipped in all future batch runs. + * 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. * 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 new file mode 100644 index 0000000..3a7ab90 --- /dev/null +++ b/pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/PromptIdentifier.java @@ -0,0 +1,40 @@ +package de.gecheckt.pdf.umbenenner.domain.model; + +import java.util.Objects; + +/** + * Stable, unique identifier for an externally-loaded prompt template. + *
+ * The prompt is not embedded in code but loaded from an external file or resource. + * The identifier allows traceability: which prompt version was used for a specific + * AI request and naming proposal? + *
+ * Identity semantics: Two {@code PromptIdentifier} instances are + * equal if and only if their identifier strings are equal (by value, not by reference). + *
+ * Typical examples: + *
+ * The choice of identifier scheme is flexible (filename, semantic version, hash, etc.), + * but must be stable and deterministic so that the same prompt always receives the + * same identifier across batch runs. + * + * @param identifier the stable, non-null identifier string (typically non-empty) + * + * @since M5 + */ +public record PromptIdentifier(String identifier) { + + /** + * Compact constructor validating that the identifier is not null. + * + * @throws NullPointerException if {@code identifier} is null + */ + public PromptIdentifier { + Objects.requireNonNull(identifier, "identifier must not be null"); + } +} 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 dc0ffbd..a6a0571 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 @@ -3,7 +3,8 @@ *
* This package contains the fundamental domain entities and status models required for document processing: *
+ * AI and naming proposal types (M5+): + *
* Additional classes: *
* Verifies that all required status values are present and correctly defined - * for M2 and future milestones. + * for M2, M5, and future milestones. */ 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); } @Test @@ -59,6 +63,18 @@ class ProcessingStatusTest { assertEquals(ProcessingStatus.PROCESSING, status); } + @Test + void readyForAiStatus_isDefinedAndAccessible() { + ProcessingStatus status = ProcessingStatus.READY_FOR_AI; + assertEquals(ProcessingStatus.READY_FOR_AI, status); + } + + @Test + void proposalReadyStatus_isDefinedAndAccessible() { + ProcessingStatus status = ProcessingStatus.PROPOSAL_READY; + assertEquals(ProcessingStatus.PROPOSAL_READY, status); + } + @Test void statusEquality_worksByReference() { // Enums have identity-based equality @@ -72,6 +88,8 @@ class ProcessingStatusTest { switch (status) { case SUCCESS -> result = "success"; + case READY_FOR_AI -> result = "ready-for-ai"; + case PROPOSAL_READY -> result = "proposal-ready"; case FAILED_RETRYABLE -> result = "retryable"; case FAILED_FINAL -> result = "final"; case SKIPPED_ALREADY_PROCESSED -> result = "skip-processed"; @@ -83,8 +101,8 @@ class ProcessingStatusTest { } @Test - void statusValues_areSixInTotal() { + void statusValues_areEightInTotal() { ProcessingStatus[] values = ProcessingStatus.values(); - assertEquals(6, values.length, "ProcessingStatus should have exactly 6 values"); + assertEquals(8, values.length, "ProcessingStatus should have exactly 8 values"); } }