From 74609e481d97ae16a12a06dd2d6ed191f01a33f7 Mon Sep 17 00:00:00 2001 From: trifonovt <87468028+TihomirTrifonov@users.noreply.github.com> Date: Mon, 23 Mar 2026 17:01:52 +0100 Subject: [PATCH] runtime-split-patch-a --- docs/architecture/RUNTIME_SPLIT_PATCH_A.md | 33 +++++++++++++ .../EmbeddingSubsystemStartupValidator.java | 3 ++ .../condition/ConditionalOnRuntimeMode.java | 17 +++++++ .../condition/RuntimeModeCondition.java | 28 +++++++++++ .../dip/runtime/config/RuntimeMode.java | 6 +++ .../runtime/config/RuntimeModeProperties.java | 16 ++++++ .../search/config/DipSearchProperties.java | 49 +++++++++++++++++++ .../PgVectorSemanticSearchEngine.java | 3 ++ .../search/web/GenericSearchController.java | 3 ++ .../camel/GenericVectorizationRoute.java | 3 ++ .../DocumentEmbeddingProcessingService.java | 3 ++ ...ConfiguredEmbeddingModelStartupRunner.java | 3 ++ .../GenericVectorizationStartupRunner.java | 3 ++ .../ted/config/LegacyTedProperties.java | 16 ++++++ src/main/resources/application-legacy.yml | 3 ++ src/main/resources/application-new.yml | 3 ++ 16 files changed, 192 insertions(+) create mode 100644 docs/architecture/RUNTIME_SPLIT_PATCH_A.md create mode 100644 src/main/java/at/procon/dip/runtime/condition/ConditionalOnRuntimeMode.java create mode 100644 src/main/java/at/procon/dip/runtime/condition/RuntimeModeCondition.java create mode 100644 src/main/java/at/procon/dip/runtime/config/RuntimeMode.java create mode 100644 src/main/java/at/procon/dip/runtime/config/RuntimeModeProperties.java create mode 100644 src/main/java/at/procon/dip/search/config/DipSearchProperties.java create mode 100644 src/main/java/at/procon/ted/config/LegacyTedProperties.java create mode 100644 src/main/resources/application-legacy.yml create mode 100644 src/main/resources/application-new.yml diff --git a/docs/architecture/RUNTIME_SPLIT_PATCH_A.md b/docs/architecture/RUNTIME_SPLIT_PATCH_A.md new file mode 100644 index 0000000..de92a8a --- /dev/null +++ b/docs/architecture/RUNTIME_SPLIT_PATCH_A.md @@ -0,0 +1,33 @@ +# Runtime split – Patch A + +This patch introduces the runtime split scaffolding without performing the full behavioral cutover. + +## Added +- `dip.runtime.mode` with values `LEGACY` / `NEW` +- `@ConditionalOnRuntimeMode` +- `DipSearchProperties` (`dip.search.*`) +- `LegacyTedProperties` (`legacy.ted.*`) as migration scaffold +- example `application-legacy.yml` and `application-new.yml` + +## First gated beans + +### LEGACY +- `GenericVectorizationRoute` +- `DocumentEmbeddingProcessingService` +- `ConfiguredEmbeddingModelStartupRunner` +- `GenericVectorizationStartupRunner` + +### NEW +- `EmbeddingSubsystemStartupValidator` +- `PgVectorSemanticSearchEngine` +- `GenericSearchController` + +## Intentional limitation of Patch A +This patch does **not** yet switch import/runtime orchestration to the new embedding job flow. +It only establishes the explicit runtime mode infrastructure and starts separating bean graphs. + +## Follow-up patch (Patch B) +- add a NEW-mode embedding job scheduler +- make generic import enqueue new embedding jobs +- disable legacy vector route in NEW mode operationally +- move active search tuning from `ted.search.*` to `dip.search.*` diff --git a/src/main/java/at/procon/dip/embedding/startup/EmbeddingSubsystemStartupValidator.java b/src/main/java/at/procon/dip/embedding/startup/EmbeddingSubsystemStartupValidator.java index 16f1871..6c63380 100644 --- a/src/main/java/at/procon/dip/embedding/startup/EmbeddingSubsystemStartupValidator.java +++ b/src/main/java/at/procon/dip/embedding/startup/EmbeddingSubsystemStartupValidator.java @@ -10,11 +10,14 @@ import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; +import at.procon.dip.runtime.condition.ConditionalOnRuntimeMode; +import at.procon.dip.runtime.config.RuntimeMode; @Component @RequiredArgsConstructor @ConditionalOnProperty(prefix = "dip.embedding", name = "enabled", havingValue = "true") @Slf4j +@ConditionalOnRuntimeMode(RuntimeMode.NEW) public class EmbeddingSubsystemStartupValidator implements ApplicationRunner { private final EmbeddingProperties properties; diff --git a/src/main/java/at/procon/dip/runtime/condition/ConditionalOnRuntimeMode.java b/src/main/java/at/procon/dip/runtime/condition/ConditionalOnRuntimeMode.java new file mode 100644 index 0000000..d43d799 --- /dev/null +++ b/src/main/java/at/procon/dip/runtime/condition/ConditionalOnRuntimeMode.java @@ -0,0 +1,17 @@ +package at.procon.dip.runtime.condition; + +import at.procon.dip.runtime.config.RuntimeMode; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.context.annotation.Conditional; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Conditional(RuntimeModeCondition.class) +public @interface ConditionalOnRuntimeMode { + RuntimeMode value(); +} diff --git a/src/main/java/at/procon/dip/runtime/condition/RuntimeModeCondition.java b/src/main/java/at/procon/dip/runtime/condition/RuntimeModeCondition.java new file mode 100644 index 0000000..50c818d --- /dev/null +++ b/src/main/java/at/procon/dip/runtime/condition/RuntimeModeCondition.java @@ -0,0 +1,28 @@ +package at.procon.dip.runtime.condition; + +import at.procon.dip.runtime.config.RuntimeMode; +import java.util.Map; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +public class RuntimeModeCondition implements Condition { + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + Map attributes = metadata.getAnnotationAttributes(ConditionalOnRuntimeMode.class.getName()); + if (attributes == null || !attributes.containsKey("value")) { + return false; + } + + RuntimeMode expected = (RuntimeMode) attributes.get("value"); + String configured = context.getEnvironment().getProperty("dip.runtime.mode", RuntimeMode.LEGACY.name()); + RuntimeMode actual; + try { + actual = RuntimeMode.valueOf(configured.trim().toUpperCase()); + } catch (Exception ex) { + actual = RuntimeMode.LEGACY; + } + return actual == expected; + } +} diff --git a/src/main/java/at/procon/dip/runtime/config/RuntimeMode.java b/src/main/java/at/procon/dip/runtime/config/RuntimeMode.java new file mode 100644 index 0000000..e8ee903 --- /dev/null +++ b/src/main/java/at/procon/dip/runtime/config/RuntimeMode.java @@ -0,0 +1,6 @@ +package at.procon.dip.runtime.config; + +public enum RuntimeMode { + LEGACY, + NEW +} diff --git a/src/main/java/at/procon/dip/runtime/config/RuntimeModeProperties.java b/src/main/java/at/procon/dip/runtime/config/RuntimeModeProperties.java new file mode 100644 index 0000000..58ae556 --- /dev/null +++ b/src/main/java/at/procon/dip/runtime/config/RuntimeModeProperties.java @@ -0,0 +1,16 @@ +package at.procon.dip.runtime.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "dip.runtime") +@Data +public class RuntimeModeProperties { + /** + * Patch A defaults to LEGACY so existing runtime behaviour is preserved until + * NEW mode is explicitly enabled. + */ + private RuntimeMode mode = RuntimeMode.LEGACY; +} diff --git a/src/main/java/at/procon/dip/search/config/DipSearchProperties.java b/src/main/java/at/procon/dip/search/config/DipSearchProperties.java new file mode 100644 index 0000000..934084d --- /dev/null +++ b/src/main/java/at/procon/dip/search/config/DipSearchProperties.java @@ -0,0 +1,49 @@ +package at.procon.dip.search.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "dip.search") +@Data +public class DipSearchProperties { + + private Lexical lexical = new Lexical(); + private Semantic semantic = new Semantic(); + private Fusion fusion = new Fusion(); + private Chunking chunking = new Chunking(); + + @Data + public static class Lexical { + private double trigramSimilarityThreshold = 0.12; + private int fulltextCandidateLimit = 120; + private int trigramCandidateLimit = 120; + } + + @Data + public static class Semantic { + private double similarityThreshold = 0.7; + private int semanticCandidateLimit = 120; + private String defaultModelKey; + } + + @Data + public static class Fusion { + private double fulltextWeight = 0.35; + private double trigramWeight = 0.20; + private double semanticWeight = 0.45; + private double recencyBoostWeight = 0.05; + private int recencyHalfLifeDays = 30; + private int debugTopHitsPerEngine = 10; + } + + @Data + public static class Chunking { + private boolean enabled = true; + private int targetChars = 1800; + private int overlapChars = 200; + private int maxChunksPerDocument = 12; + private int startupLexicalBackfillLimit = 500; + } +} diff --git a/src/main/java/at/procon/dip/search/engine/semantic/PgVectorSemanticSearchEngine.java b/src/main/java/at/procon/dip/search/engine/semantic/PgVectorSemanticSearchEngine.java index 15fdff4..6bbca48 100644 --- a/src/main/java/at/procon/dip/search/engine/semantic/PgVectorSemanticSearchEngine.java +++ b/src/main/java/at/procon/dip/search/engine/semantic/PgVectorSemanticSearchEngine.java @@ -14,10 +14,13 @@ import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import at.procon.dip.runtime.condition.ConditionalOnRuntimeMode; +import at.procon.dip.runtime.config.RuntimeMode; @Component @RequiredArgsConstructor @Slf4j +@ConditionalOnRuntimeMode(RuntimeMode.NEW) public class PgVectorSemanticSearchEngine implements SearchEngine { private final EmbeddingProperties embeddingProperties; diff --git a/src/main/java/at/procon/dip/search/web/GenericSearchController.java b/src/main/java/at/procon/dip/search/web/GenericSearchController.java index 752af22..f0941cc 100644 --- a/src/main/java/at/procon/dip/search/web/GenericSearchController.java +++ b/src/main/java/at/procon/dip/search/web/GenericSearchController.java @@ -14,10 +14,13 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import at.procon.dip.runtime.condition.ConditionalOnRuntimeMode; +import at.procon.dip.runtime.config.RuntimeMode; @RestController @RequestMapping("/search") @RequiredArgsConstructor +@ConditionalOnRuntimeMode(RuntimeMode.NEW) public class GenericSearchController { private final SearchOrchestrator searchOrchestrator; diff --git a/src/main/java/at/procon/dip/vectorization/camel/GenericVectorizationRoute.java b/src/main/java/at/procon/dip/vectorization/camel/GenericVectorizationRoute.java index 8330325..4606409 100644 --- a/src/main/java/at/procon/dip/vectorization/camel/GenericVectorizationRoute.java +++ b/src/main/java/at/procon/dip/vectorization/camel/GenericVectorizationRoute.java @@ -14,6 +14,8 @@ import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Component; import at.procon.ted.config.TedProcessorProperties; +import at.procon.dip.runtime.condition.ConditionalOnRuntimeMode; +import at.procon.dip.runtime.config.RuntimeMode; import java.util.List; import java.util.UUID; @@ -24,6 +26,7 @@ import java.util.UUID; @Component @RequiredArgsConstructor @Slf4j +@ConditionalOnRuntimeMode(RuntimeMode.LEGACY) public class GenericVectorizationRoute extends RouteBuilder { private static final String ROUTE_ID_TRIGGER = "generic-vectorization-trigger"; diff --git a/src/main/java/at/procon/dip/vectorization/service/DocumentEmbeddingProcessingService.java b/src/main/java/at/procon/dip/vectorization/service/DocumentEmbeddingProcessingService.java index b81c022..1deb93a 100644 --- a/src/main/java/at/procon/dip/vectorization/service/DocumentEmbeddingProcessingService.java +++ b/src/main/java/at/procon/dip/vectorization/service/DocumentEmbeddingProcessingService.java @@ -9,6 +9,8 @@ import at.procon.ted.config.TedProcessorProperties; import at.procon.ted.model.entity.VectorizationStatus; import at.procon.ted.repository.ProcurementDocumentRepository; import at.procon.ted.service.VectorizationService; +import at.procon.dip.runtime.condition.ConditionalOnRuntimeMode; +import at.procon.dip.runtime.config.RuntimeMode; import java.time.OffsetDateTime; import java.util.UUID; import lombok.RequiredArgsConstructor; @@ -26,6 +28,7 @@ import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @Slf4j +@ConditionalOnRuntimeMode(RuntimeMode.LEGACY) public class DocumentEmbeddingProcessingService { private final DocumentEmbeddingRepository embeddingRepository; diff --git a/src/main/java/at/procon/dip/vectorization/startup/ConfiguredEmbeddingModelStartupRunner.java b/src/main/java/at/procon/dip/vectorization/startup/ConfiguredEmbeddingModelStartupRunner.java index 6aff6e8..7187954 100644 --- a/src/main/java/at/procon/dip/vectorization/startup/ConfiguredEmbeddingModelStartupRunner.java +++ b/src/main/java/at/procon/dip/vectorization/startup/ConfiguredEmbeddingModelStartupRunner.java @@ -3,6 +3,8 @@ package at.procon.dip.vectorization.startup; import at.procon.dip.domain.document.service.DocumentEmbeddingService; import at.procon.dip.domain.document.service.command.RegisterEmbeddingModelCommand; import at.procon.ted.config.TedProcessorProperties; +import at.procon.dip.runtime.condition.ConditionalOnRuntimeMode; +import at.procon.dip.runtime.config.RuntimeMode; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.ApplicationArguments; @@ -15,6 +17,7 @@ import org.springframework.stereotype.Component; @Component @RequiredArgsConstructor @Slf4j +@ConditionalOnRuntimeMode(RuntimeMode.LEGACY) public class ConfiguredEmbeddingModelStartupRunner implements ApplicationRunner { private final TedProcessorProperties properties; diff --git a/src/main/java/at/procon/dip/vectorization/startup/GenericVectorizationStartupRunner.java b/src/main/java/at/procon/dip/vectorization/startup/GenericVectorizationStartupRunner.java index 5266c62..31dd216 100644 --- a/src/main/java/at/procon/dip/vectorization/startup/GenericVectorizationStartupRunner.java +++ b/src/main/java/at/procon/dip/vectorization/startup/GenericVectorizationStartupRunner.java @@ -3,6 +3,8 @@ package at.procon.dip.vectorization.startup; import at.procon.dip.domain.document.EmbeddingStatus; import at.procon.dip.domain.document.repository.DocumentEmbeddingRepository; import at.procon.ted.config.TedProcessorProperties; +import at.procon.dip.runtime.condition.ConditionalOnRuntimeMode; +import at.procon.dip.runtime.config.RuntimeMode; import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; @@ -19,6 +21,7 @@ import org.springframework.stereotype.Component; @Component @RequiredArgsConstructor @Slf4j +@ConditionalOnRuntimeMode(RuntimeMode.LEGACY) public class GenericVectorizationStartupRunner implements ApplicationRunner { private static final int BATCH_SIZE = 1000; diff --git a/src/main/java/at/procon/ted/config/LegacyTedProperties.java b/src/main/java/at/procon/ted/config/LegacyTedProperties.java new file mode 100644 index 0000000..b06e2ce --- /dev/null +++ b/src/main/java/at/procon/ted/config/LegacyTedProperties.java @@ -0,0 +1,16 @@ +package at.procon.ted.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * Patch A scaffold for the legacy runtime configuration tree. + * + * The legacy runtime still uses {@link TedProcessorProperties} today. This class is + * introduced so the old configuration can be moved gradually from `ted.*` to + * `legacy.ted.*` without blocking the runtime split. + */ +@Configuration +@ConfigurationProperties(prefix = "legacy.ted") +public class LegacyTedProperties extends TedProcessorProperties { +} diff --git a/src/main/resources/application-legacy.yml b/src/main/resources/application-legacy.yml new file mode 100644 index 0000000..ff37b35 --- /dev/null +++ b/src/main/resources/application-legacy.yml @@ -0,0 +1,3 @@ +dip: + runtime: + mode: LEGACY diff --git a/src/main/resources/application-new.yml b/src/main/resources/application-new.yml new file mode 100644 index 0000000..efd8661 --- /dev/null +++ b/src/main/resources/application-new.yml @@ -0,0 +1,3 @@ +dip: + runtime: + mode: NEW