Review: Full prototype implementation#49
Draft
Schmarvinius wants to merge 48 commits into
Draft
Conversation
tenant scenarios and update README
…tup.java Co-authored-by: Matthias Braun <matthia.braun@sap.com>
Co-authored-by: Simon Engel <simon.engel01@sap.com>
Co-authored-by: Simon Engel <simon.engel01@sap.com>
Replaces the single-module srv/ codebase with the multi-module Maven layout developed on the internal SAP fork. Adds LICENSE at root (per OSPO finding), pom.xml SCM section, and fixes the broken .reuse/dep5 link in CONTRIBUTING.md. The .github/ workflows and gh_ruleset.json from this repo are preserved. Note: integration-tests/spring tests fail on clean clone because the build does not invoke npm install before cds build, so the @cap-js/ai node plugin is not available when the csn is generated. Pre-existing issue on the fork's main; tracked separately.
Co-authored-by: hyperspace-insights[bot] <209611008+hyperspace-insights[bot]@users.noreply.github.com>
* chore: bootstrap ci/cd workflows * chore: address pr bot review findings * fix: specify ai-core service instance and key * rename blackduck identifier
* fix: run npm ci before cds build in integration-tests (#17) Without `npm ci`, `cds build` ran in `integration-tests/spring/` without the `@cap-js/ai` Node plugin, so the generated CSN was missing the synthetic `SAP_Recommendations` association and 8 RecommendationTest cases failed on clean clone with `JsonPath $['SAP_Recommendations']: PathNotFoundException`. Add `cds.install-node` and `cds.npm-ci` executions to the cds-maven-plugin block in `integration-tests/spring/pom.xml`, mirroring the pattern already used in `samples/bookshop/srv/pom.xml`. CI continues to skip the Node download via `-Dcds.install-node.skip`; clean local clones get a self-contained Node + `npm ci` into `integration-tests/node_modules/`. Commit `package-lock.json` for `integration-tests/` and `samples/bookshop/` so `npm ci` is reproducible, and remove the `**/package-lock.json` entry from `.gitignore`. Closes #17 * fix: regenerate lockfiles against public npm registry The @cap-js/ai entry in the previous lockfiles pointed at int.repositories.cloud.sap (SAP-internal Artifactory), which is unreachable from GitHub-hosted runners and broke `npm ci` in CI with EAI_AGAIN. Regenerated both lockfiles with the public registry forced for the @cap-js scope so they resolve from registry.npmjs.org. * fix: invert integration-test modules profile and suppress AICore kinds models Two fixes wrapped together since they both unblock CI on PR #21. 1. Maven `<modules>` is additive in profiles, so `skip-integration-tests` was a no-op — it re-listed the source modules instead of removing the integration ones. Invert the structure: default modules are source- only; a `with-integration-tests` profile (active by default) adds `integration-tests` and `coverage-report`. CI consumers switch from `-P skip-integration-tests` to `-P '!with-integration-tests'`. 2. In CI, `cds bind --exec` writes a `[hybrid]` profile entry to `.cdsrc-private.json` for the `ai-core` binding, kind `AICore-btp`. `cds build` then expands the kind's `model: "@cap-js/ai/srv/AICoreService"` into the build's model list, which collides with the `AICore` service shipped by `cds-feature-ai-core` (loaded via `using { AICore } from 'com.sap.cds/ai'`). The existing `requires.AICore.model = false` override only suppresses the named-require model, not the kind's. Add `requires.kinds.AICore-btp.model = false` (and the same for `AICore-mocked`) so the kind never contributes a model to cds build. * fix: build root modules before sonar scan; dispatch event handlers on registered service type - SonarQube scan job ran `mvn clean verify` from `integration-tests/` without first installing the source modules, so the integration-tests build couldn't resolve `cds-feature-ai-core` etc. as JAR dependencies. Mirror the integration-tests action: install the three source modules with `-pl ... -am -DskipTests` first, then run the scan build. - `AICoreServiceConfiguration.eventHandlers` recomputed `hasAICoreBinding` separately from `services()` and unconditionally cast the registered service to `AICoreServiceImpl`. If the two calls disagreed (or if another configurer registered a Mock first), the cast threw `ClassCastException: MockAICoreServiceImpl cannot be cast to AICoreServiceImpl`. Read the actually-registered service from the catalog and dispatch via `instanceof` patterns so the handler set always matches the live registration.
The Fiori recommendation handler used to emit a SAP_Recommendations entry for every column in the prediction response, regardless of whether the user had already filled that column. This worked locally because the mock client only returns predictions for cells marked [PREDICT], but it broke in CI where the real RPT model returns predictions for every target column. Filter the response to columns that were null in the input row so behaviour is consistent across both clients. Adds a regression unit test that simulates the RPT-style response shape.
#25) * test(itest): disable ResourceGroup mutating tests pending admin scopes (#24) The 4 CREATE/DELETE tests in ResourceGroupTest fail in CI with HTTP 403 because /v2/admin/resourceGroups requires AI Core tenant-admin scopes (see SAP AI Core service guide §3.2 / §6.3), which the bound CI service key (sap-internal plan, technical user cap.calesi.munich@sap.com) does not carry. Disable them with @disabled until the IAM situation is sorted; read-only readAll_returnsResourceGroups stays enabled. Refs #24 * fix(itest): repair resource-group cleanup so quota does not leak (#24) The test extension's pre-cleanup never ran in CI because it gated on AICORE_SERVICE_KEY, but the bound credentials are exposed via VCAP_SERVICES. 48 itest-rg-* groups had leaked into the cdsmunich tenant, hitting the 50-group hard quota and turning every CREATE into a 400/quota error. - Remove the env-var guard from ResourceGroupCleanupExtension so the ResourceGroupApi auto-discovers credentials from VCAP_SERVICES (same pattern as production AICoreServiceImpl). - Add cleanup-resource-groups.cjs and invoke it with if: always() in the integration-tests action, mirroring cap-js/ai. Survives JVM crashes. - Re-enable the four ResourceGroupTest CREATE/DELETE tests. * chore(itest): drop unused .cjs cleanup script The JUnit ResourceGroupCleanupExtension already handles cleanup exhaustively in @BeforeAll/@afterall — verified green in the previous CI run. The .cjs script failed with 'No impl configured for cds.requires.kinds.AICore-btp' anyway and was wrapped in `|| true`, so it provided no real safety net.
Bumps the minor-patch group with 5 updates in the / directory: | Package | From | To | | --- | --- | --- | | [com.github.ben-manes.caffeine:caffeine](https://github.com/ben-manes/caffeine) | `3.2.0` | `3.2.4` | | [org.apache.maven.plugins:maven-enforcer-plugin](https://github.com/apache/maven-enforcer) | `3.6.2` | `3.6.3` | | [com.diffplug.spotless:spotless-maven-plugin](https://github.com/diffplug/spotless) | `3.4.0` | `3.5.0` | | [com.sap.ai.sdk:core](https://github.com/SAP/ai-sdk-java) | `1.18.0` | `1.19.0` | | [com.sap.ai.sdk.foundationmodels:sap-rpt](https://github.com/SAP/ai-sdk-java) | `1.18.0` | `1.19.0` | Updates `com.github.ben-manes.caffeine:caffeine` from 3.2.0 to 3.2.4 - [Release notes](https://github.com/ben-manes/caffeine/releases) - [Commits](ben-manes/caffeine@v3.2.0...v3.2.4) Updates `org.apache.maven.plugins:maven-enforcer-plugin` from 3.6.2 to 3.6.3 - [Release notes](https://github.com/apache/maven-enforcer/releases) - [Commits](apache/maven-enforcer@enforcer-3.6.2...enforcer-3.6.3) Updates `com.diffplug.spotless:spotless-maven-plugin` from 3.4.0 to 3.5.0 - [Release notes](https://github.com/diffplug/spotless/releases) - [Changelog](https://github.com/diffplug/spotless/blob/main/CHANGES.md) - [Commits](diffplug/spotless@maven/3.4.0...maven/3.5.0) Updates `com.sap.ai.sdk:core` from 1.18.0 to 1.19.0 - [Release notes](https://github.com/SAP/ai-sdk-java/releases) - [Changelog](https://github.com/SAP/ai-sdk-java/blob/main/docs/release_notes.md) - [Commits](SAP/ai-sdk-java@rel/1.18.0...rel/1.19.0) Updates `com.sap.ai.sdk.foundationmodels:sap-rpt` from 1.18.0 to 1.19.0 - [Release notes](https://github.com/SAP/ai-sdk-java/releases) - [Changelog](https://github.com/SAP/ai-sdk-java/blob/main/docs/release_notes.md) - [Commits](SAP/ai-sdk-java@rel/1.18.0...rel/1.19.0) Updates `com.sap.ai.sdk.foundationmodels:sap-rpt` from 1.18.0 to 1.19.0 - [Release notes](https://github.com/SAP/ai-sdk-java/releases) - [Changelog](https://github.com/SAP/ai-sdk-java/blob/main/docs/release_notes.md) - [Commits](SAP/ai-sdk-java@rel/1.18.0...rel/1.19.0) --- updated-dependencies: - dependency-name: com.github.ben-manes.caffeine:caffeine dependency-version: 3.2.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: org.apache.maven.plugins:maven-enforcer-plugin dependency-version: 3.6.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: com.diffplug.spotless:spotless-maven-plugin dependency-version: 3.5.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: com.sap.ai.sdk:core dependency-version: 1.19.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: com.sap.ai.sdk.foundationmodels:sap-rpt dependency-version: 1.19.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: com.sap.ai.sdk.foundationmodels:sap-rpt dependency-version: 1.19.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…#27) * fix(mtx): wire MTX subscribe lifecycle for mock AI Core service (#26) The local-with-tenants profile didn't set cds.requires.AICore.multiTenancy, so AICoreServiceImpl.isMultiTenancyEnabled() returned false and seven MTX integration tests failed. Even with the flag, the mock service had no SubscribeEventContext handler, so the tenant resource-group cache was never populated and the cache-based assertions still failed. - Enable cds.requires.AICore.multiTenancy under the local-with-tenants profile so MT registers in mtx-local integration tests. - Add MockAICoreSetupHandler mirroring AICoreSetupHandler's subscribe/ unsubscribe lifecycle in-memory (no AI Core API calls). - Register the mock setup handler from AICoreServiceConfiguration when the mock service runs with MT enabled. - Use AICoreService interface in mtx-local integration tests; drop the AICoreServiceImpl casts and the assumeTrue skips that those casts forced. * ci: split mtx-local into its own job without AI Core binding (#26) Running spring + mtx-local under one cds-bind made mtx-local inherit the AI Core binding, so AICoreServiceConfiguration registered the real AICoreServiceImpl and AICoreSetupHandler hit the live API on every subscribe/unsubscribe (HTTP 409 on resource-group delete in CI). The mtx-local module is intended to run in-memory against the mock service. - Add a Local MTX Tests job (matrix Java 17/21, no cf-bind) that runs mvn -pl integration-tests/mtx-local/srv -am -P mtx-integration-tests. - Drop the mtx-integration-tests profile from the integration-tests composite action so its cds-bind invocation runs only the spring reactor (the default). * change default rg and add provisioning to itest * fix(itest): avoid 404 on keyed RG read by listing then filtering A keyed READ on AICore.resourceGroups routes through ResourceGroupHandler.onRead -> resourceGroupApi.get(rgId), which returns 404 for a not-yet-existing RG instead of an empty list. Use a non-keyed Select and filter client-side so the helper can detect absence and INSERT the RG. * fix(itest): increase RPT deployment poll budget to ~6 min Prior 10-attempt cap (~145 s sleep budget) was right at the edge of the observed RPT-1 deployment time (~150 s) and timed out in CI. Bumping to 18 attempts gives a ~6 min ceiling so cold deployments have headroom without making genuinely failed runs hang indefinitely. * fix(itest): tune RPT poll budget to 15 attempts (~4.7 min) 18 attempts gave too much headroom; 15 is enough margin over the observed ~150 s RPT-1 deployment time without making genuinely stuck runs hang for too long. * fix(itest): scope cleanup to label-owned RGs to protect other CF instances AI Core resource groups are shared across all CF service instances bound to the same AI Core tenant, so listing every RG and stopping its deployments before delete was destroying work owned by other instances. Adopt cap-js/ai's pattern (srv/ai-core/resourceGroups.js) of stamping each RG with an owner label at create time and filtering by it on cleanup, but use a distinct key (CDS_FEATURE_AI_ITEST_OWNER) so the itest cleanup never collides with prod tenant cleanup. Cleanup also short-circuits when the owner is unset or equal to the local-dev RG name, so dev runs reuse their RG across invocations. While here, drop the test scaffold's hand-rolled config/deployment helpers and call the existing AICoreServiceImpl.deploymentId(...) which already does lookup-or-create-with-polling. The RG insert + provisioned poll stay in test scope per design. * fix(itest): drop redundant null-check on rg.getLabels() BckndResourceGroup.getLabels() is @nonnull, so the null guard was flagged by SpotBugs (RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE). * fix(ci): resolve integration test and SonarQube pipeline failures - ResourceGroupCleanupExtension: defer afterAll cleanup to end of test suite using CloseableResource pattern, preventing premature deletion of the resource group before all test classes have completed - TenantIsolationTest: catch Throwable (not just Exception) in tearDown so MockMvc AssertionError does not escape the safety catch blocks - SonarQube scan: remove -P mtx-integration-tests from the verify step; mtx-local tests already run in their dedicated local-mtx-tests job and including them under cds bind causes real AI Core API calls that conflict with the mock-based test design - BaseIntegrationTest: expose clearDeploymentIdCache() so the cleanup extension can invalidate stale cached deployment IDs after deletion * fix(itest): prevent beforeAll cleanup from deleting active resource group The ResourceGroupCleanupExtension.beforeAll() was deleting the resource group that AICoreServiceTest (which runs first) had just created, causing ActionTest and all Recommendation tests to fail with 400/500 from AI Core. - Remove beforeAll/cleanupOnce from ResourceGroupCleanupExtension; the deferred afterAll (CloseableResource at suite end) is sufficient - Expose ensureResourceGroupProvisioned() as protected in BaseIntegrationTest - Add @BeforeAll to ActionTest to guarantee the resource group is provisioned before deploymentId tests run, regardless of class order * fix(ci): generate JaCoCo XML reports for SonarQube scan - Add jacoco report goal to cds-feature-ai-core and integration-tests/spring pom.xml (they only had prepare-agent, no report generation) - Move mtx-local dependency in coverage-report to mtx-integration-tests profile so the aggregate report can be built without mtx-local - Update scan-with-sonar action: remove -DskipTests so unit tests run and generate coverage; add install to integration tests step; add step to build coverage-report aggregate; point sonar at the aggregate report * fix(itest): prevent ResourceGroupTest from leaking resource groups ResourceGroupTest creates itest-rg-* groups during tests but AfterEach cleanup silently failed (409 Conflict) because the RG wasn't provisioned yet. Additionally, the ResourceGroupCleanupExtension only cleaned label-owned groups, missing the itest-rg-* prefix groups entirely. - ResourceGroupTest: wait for RG to be provisioned before delete in AfterEach, matching the pattern used in delete_resourceGroup test - ResourceGroupCleanupExtension: add prefix-based safety-net cleanup that always deletes any itest-rg-* groups at suite end, regardless of whether CDS_AICORE_TEST_RESOURCE_GROUP is set - scan-with-sonar: set CDS_AICORE_TEST_RESOURCE_GROUP env var so the cleanup extension actually runs in the SonarQube scan job * fix(itest): stop_deployment test must use own RG, not 'default' The test was hardcoded to query and stop the first RUNNING deployment in the 'default' resource group. Since AI Core is scoped at the subaccount level, this stopped deployments from other CF spaces sharing the same subaccount (e.g. orchestration deployments). Use getDefaultResourceGroup() which resolves to the CI-specific itest-* resource group, ensuring only test-owned deployments are affected. * fix(itest): remove all hardcoded 'default' resource group references DeploymentTest.update_targetStatus_stopsRunningDeployment was still hardcoded to query and stop deployments in the 'default' resource group, which stopped the user's orchestration deployment in the shared subaccount. Also fix ConfigurationTest to avoid creating orphan configs in 'default'. All test classes now use getDefaultResourceGroup() which resolves to the CI-specific itest-* resource group via CDS_AICORE_TEST_RESOURCE_GROUP. * fix: retry on HTTP 404 from AI Core inference endpoint AI Core deployments report RUNNING status before the inference gateway has fully propagated the route. This causes 404 responses for a short window after deployment creation. The retry logic already handled 403 and 412 as transient 'not ready yet' states but was missing 404. Adding 404 to the retryable status codes makes inference calls resilient to this AI Core eventual-consistency window. * fix: cap exponential backoff at 30s and increase itest retry budget The exponential backoff with no cap resulted in extremely long final intervals (77s, 154s) leaving only ~153s of total retry window across 10 attempts. For fresh deployments where inference needs up to 5 min to become stable, this was insufficient. - Cap max interval at 30s so retries stay frequent - Increase integration test maxRetries to 15 (gives ~5.5 min window) - Together with the 404 retry fix, this handles AI Core's deployment warm-up period reliably * fix(ci): move RG cleanup to dedicated job after all itests complete When Java 17 and Java 21 integration tests run in parallel, they create separate deployments of the same RPT model. When the first-finishing job cleaned up (stopped its deployment + deleted its RG), AI Core tore down shared model serving infrastructure causing the still-running job's inference calls to fail with 404. - ResourceGroupCleanupExtension: afterAll now only cleans itest-rg-* leaked groups (safety net), no longer stops/deletes the main test RG - pipeline.yml: add dedicated 'integration-tests-cleanup' job that runs with if:always() after BOTH integration test jobs complete, then deletes all resource groups for the current run * fix(ci): remove dead code flagged by PMD PMD caught unused deleteOwnedResourceGroups, deleteLabeledResourceGroups, stopDeploymentsInResourceGroup, waitForDeploymentsStopped, and ownsResourceGroup methods left over from moving RG cleanup to CI job. Also remove unused clearDeploymentIdCache(). * fix(itest): disable stop_deployment tests that kill shared RPT deployment ActionTest.stop_deployment_changesTargetStatus and DeploymentTest. update_targetStatus_stopsRunningDeployment stop the RPT deployment created by AICoreServiceTest. Subsequent Recommendation tests then hit 404 from the inference endpoint for the full retry budget (~250s) because the stopped deployment's inference route is gone and the newly created replacement deployment needs warmup time. Disable both tests until they are refactored to create and stop their own isolated deployment instead of the shared one. * fix(ci): cleanup job reads credentials from VCAP_SERVICES not AICORE_SERVICE_KEY cds bind --exec injects credentials via VCAP_SERVICES, not AICORE_SERVICE_KEY. The cleanup script was trying to parse AICORE_SERVICE_KEY which was undefined, causing a JSON parse error.
…40) - Replace string concatenation in ServiceException messages with SLF4J-style {} placeholders in AICoreSetupHandler (3 locations) - Replace Collectors.toList() with .toList() in AbstractCrudHandler - Replace new ArrayList<>() with List.of() for null case - Remove unused imports (ArrayList, Collectors) Closes #32
…ler with record (#41) * fix: use idiomatic patterns for error messages and stream collection - Replace string concatenation in ServiceException messages with SLF4J-style {} placeholders in AICoreSetupHandler (3 locations) - Replace Collectors.toList() with .toList() in AbstractCrudHandler - Replace new ArrayList<>() with List.of() for null case - Remove unused imports (ArrayList, Collectors) Closes #32 * refactor: use cds-maven-plugin generated interfaces for type-safe entity access Replace manual AICoreElements constants class with generated typed interfaces from cds-maven-plugin (following cds-feature-attachments pattern): - Add cds-maven-plugin generate goal to cds-feature-ai-core/pom.xml - Generated interfaces: Deployments, Configurations, ResourceGroups, BckndResourceGroupLabel (with constants + typed getters/setters) - Update all handlers to use generated constants (e.g., Deployments.ID) and typed factory methods (e.g., Deployments.create(), ResourceGroups.create(id)) - Remove manual AICoreElements.java (replaced by code generation) - DeploymentHandler: use Deployments.create() with typed setters instead of 17-parameter buildDeploymentData method Closes #33 * fix: use explicit types and remove unnecessary cast in ConfigurationHandler - Use List<ParameterArgumentBinding> instead of var for bindings - Use ParameterArgumentBinding instead of var for bm - Remove unnecessary (CdsData) cast since ParameterArgumentBinding extends CdsData * fix: use ArtifactArgumentBinding generated interface for inputArtifactBindings Replace raw CdsData with typed ArtifactArgumentBinding.create() and setters for the inputArtifactBindings mapping in ConfigurationHandler. * refactor: use typed data parameters in handler method signatures Use CAP Java's handler data parameter injection for onCreate/onUpdate: - DeploymentHandler.onCreate(ctx, List<Deployments>) - DeploymentHandler.onUpdate(ctx, List<Deployments>) - ConfigurationHandler.onCreate(ctx, List<Configurations>) - ResourceGroupHandler.onCreate(ctx, List<ResourceGroups>) This eliminates manual CQN entry extraction (context.getCqn().entries()) and enables typed getters (entry.getName() vs entry.get("name")). Update tests to pass data as the second parameter directly. * fix: remove unused CdsData import in ConfigurationHandler
* fix: use idiomatic patterns for error messages and stream collection
- Replace string concatenation in ServiceException messages with SLF4J-style
{} placeholders in AICoreSetupHandler (3 locations)
- Replace Collectors.toList() with .toList() in AbstractCrudHandler
- Replace new ArrayList<>() with List.of() for null case
- Remove unused imports (ArrayList, Collectors)
Closes #32
* refactor: use cds-maven-plugin generated interfaces for type-safe entity access
Replace manual AICoreElements constants class with generated typed
interfaces from cds-maven-plugin (following cds-feature-attachments pattern):
- Add cds-maven-plugin generate goal to cds-feature-ai-core/pom.xml
- Generated interfaces: Deployments, Configurations, ResourceGroups,
BckndResourceGroupLabel (with constants + typed getters/setters)
- Update all handlers to use generated constants (e.g., Deployments.ID)
and typed factory methods (e.g., Deployments.create(), ResourceGroups.create(id))
- Remove manual AICoreElements.java (replaced by code generation)
- DeploymentHandler: use Deployments.create() with typed setters instead
of 17-parameter buildDeploymentData method
Closes #33
* refactor: slim AICoreService interface to public API surface
Remove implementation-detail methods from AICoreService interface:
- getDefaultResourceGroup()
- getResourceGroupPrefix()
- getTenantResourceGroupCache()
- getResourceGroupDeploymentCache()
- clearTenantCache()
- resolveResourceGroupFromKeys()
These methods remain public on AICoreServiceImpl and MockAICoreServiceImpl
but are no longer part of the service contract. Only domain-level API
(resourceGroupForTenant, deploymentId, inferenceClient, isMultiTenancyEnabled)
and getRetry() (cross-module consumer) remain on the interface.
Closes #34
* refactor: introduce AbstractAICoreService base class for shared internal methods
Extract getDefaultResourceGroup(), getResourceGroupPrefix(),
getTenantResourceGroupCache(), getResourceGroupDeploymentCache(),
clearTenantCache(), and resolveResourceGroupFromKeys() into an abstract
base class that both AICoreServiceImpl and MockAICoreServiceImpl extend.
This keeps the public AICoreService interface slim (domain API only)
while providing a typed common base for tests and internal consumers
that need access to cache/config methods.
Update all integration and MTX test files to use the new
getAICoreServiceImpl() helper that returns AbstractAICoreService.
Closes #34
) * fix: use idiomatic patterns for error messages and stream collection - Replace string concatenation in ServiceException messages with SLF4J-style {} placeholders in AICoreSetupHandler (3 locations) - Replace Collectors.toList() with .toList() in AbstractCrudHandler - Replace new ArrayList<>() with List.of() for null case - Remove unused imports (ArrayList, Collectors) Closes #32 * refactor: use cds-maven-plugin generated interfaces for type-safe entity access Replace manual AICoreElements constants class with generated typed interfaces from cds-maven-plugin (following cds-feature-attachments pattern): - Add cds-maven-plugin generate goal to cds-feature-ai-core/pom.xml - Generated interfaces: Deployments, Configurations, ResourceGroups, BckndResourceGroupLabel (with constants + typed getters/setters) - Update all handlers to use generated constants (e.g., Deployments.ID) and typed factory methods (e.g., Deployments.create(), ResourceGroups.create(id)) - Remove manual AICoreElements.java (replaced by code generation) - DeploymentHandler: use Deployments.create() with typed setters instead of 17-parameter buildDeploymentData method Closes #33 * refactor: slim AICoreService interface to public API surface Remove implementation-detail methods from AICoreService interface: - getDefaultResourceGroup() - getResourceGroupPrefix() - getTenantResourceGroupCache() - getResourceGroupDeploymentCache() - clearTenantCache() - resolveResourceGroupFromKeys() These methods remain public on AICoreServiceImpl and MockAICoreServiceImpl but are no longer part of the service contract. Only domain-level API (resourceGroupForTenant, deploymentId, inferenceClient, isMultiTenancyEnabled) and getRetry() (cross-module consumer) remain on the interface. Closes #34 * refactor: decompose FioriRecommendationHandler into focused helpers Extract two helper classes from the 400-line handler: - RecommendationContextBuilder: entity analysis, prediction element discovery, context query building, synthetic key computation, and row assembly - RecommendationResultParser: prediction value type coercion, text path resolution from annotations, description DB lookups, and final recommendations map assembly The handler is now a slim orchestrator (~110 lines) that delegates complex logic to the helpers. Also replace unbounded ConcurrentHashMap.newKeySet() with a bounded Caffeine cache (max 10,000 entries) for the entity negative-result cache. Closes #35 * fix: add explicit caffeine dependency; make computeSyntheticKey private - Add caffeine dependency to cds-feature-recommendations/pom.xml (previously relied on transitive from cds-feature-ai-core) - Make computeSyntheticKey private in RecommendationContextBuilder (only called internally from assembleRows)
* fix: use idiomatic patterns for error messages and stream collection
- Replace string concatenation in ServiceException messages with SLF4J-style
{} placeholders in AICoreSetupHandler (3 locations)
- Replace Collectors.toList() with .toList() in AbstractCrudHandler
- Replace new ArrayList<>() with List.of() for null case
- Remove unused imports (ArrayList, Collectors)
Closes #32
* refactor: use cds-maven-plugin generated interfaces for type-safe entity access
Replace manual AICoreElements constants class with generated typed
interfaces from cds-maven-plugin (following cds-feature-attachments pattern):
- Add cds-maven-plugin generate goal to cds-feature-ai-core/pom.xml
- Generated interfaces: Deployments, Configurations, ResourceGroups,
BckndResourceGroupLabel (with constants + typed getters/setters)
- Update all handlers to use generated constants (e.g., Deployments.ID)
and typed factory methods (e.g., Deployments.create(), ResourceGroups.create(id))
- Remove manual AICoreElements.java (replaced by code generation)
- DeploymentHandler: use Deployments.create() with typed setters instead
of 17-parameter buildDeploymentData method
Closes #33
* refactor: slim AICoreService interface to public API surface
Remove implementation-detail methods from AICoreService interface:
- getDefaultResourceGroup()
- getResourceGroupPrefix()
- getTenantResourceGroupCache()
- getResourceGroupDeploymentCache()
- clearTenantCache()
- resolveResourceGroupFromKeys()
These methods remain public on AICoreServiceImpl and MockAICoreServiceImpl
but are no longer part of the service contract. Only domain-level API
(resourceGroupForTenant, deploymentId, inferenceClient, isMultiTenancyEnabled)
and getRetry() (cross-module consumer) remain on the interface.
Closes #34
* refactor: decompose FioriRecommendationHandler into focused helpers
Extract two helper classes from the 400-line handler:
- RecommendationContextBuilder: entity analysis, prediction element
discovery, context query building, synthetic key computation, and
row assembly
- RecommendationResultParser: prediction value type coercion, text path
resolution from annotations, description DB lookups, and final
recommendations map assembly
The handler is now a slim orchestrator (~110 lines) that delegates
complex logic to the helpers.
Also replace unbounded ConcurrentHashMap.newKeySet() with a bounded
Caffeine cache (max 10,000 entries) for the entity negative-result cache.
Closes #35
* refactor: accept SDK API clients as constructor parameters
Add overloaded constructor to AICoreServiceImpl that accepts
DeploymentApi, ConfigurationApi, ResourceGroupApi, and AiCoreService
as parameters. The existing convenience constructor delegates to it
with default instances.
This enables direct injection of mocked clients in unit tests without
needing to mock getter methods on the service itself.
Closes #36
* refactor: remove convenience constructor, pass API clients explicitly
Remove the delegating convenience constructor from AICoreServiceImpl.
The only caller (AICoreServiceConfiguration) now passes DeploymentApi,
ConfigurationApi, ResourceGroupApi, and AiCoreService explicitly,
making the dependency wiring fully transparent at the creation site.
Closes #36
This was referenced Jun 9, 2026
Closed
* refactor(ai-core): move public API to dedicated api package Move AICoreService and ModelDeploymentSpec from com.sap.cds.feature.aicore.core to com.sap.cds.feature.aicore.api so the public contract is clearly separated from the internal implementation. Add a package-info.java describing the api package. Internal classes (AbstractAICoreService, AICoreServiceImpl, MockAICoreServiceImpl, AICoreServiceConfiguration, handlers) and the unit test for AICoreServiceImpl get explicit imports for the relocated types. Implementation references in AICoreService javadoc use FQCN @link to avoid coupling the interface to internals. Addresses review feedback on PR #49 (move APIs to api package). * refactor(recommendations): move public API to dedicated api package Move RecommendationClient, RecommendationClientResolver, RptInferenceClient and RptModelSpec from com.sap.cds.feature.recommendation to com.sap.cds.feature.recommendation.api so the public contract is clearly separated from the internal implementation. Add a package-info.java for the api package. RecommendationClient and RecommendationClientResolver are promoted from package-private to public to be usable from outside the plugin. Internal classes (FioriRecommendationHandler, MockRecommendationClient, RecommendationConfiguration) and tests get explicit imports for the relocated types. Addresses review feedback on PR #49 (move APIs to api package). * test(integration): update imports for relocated api packages Update test imports in integration-tests/spring and integration-tests/mtx-local to reference AICoreService, ModelDeploymentSpec and RptModelSpec from their new api packages. Addresses review feedback on PR #49 (move APIs to api package). * samples(bookshop): update imports for relocated api packages Update AICoreShowcaseHandler imports to reference AICoreService, RptInferenceClient and RptModelSpec from their new api packages. Addresses review feedback on PR #49 (move APIs to api package).
Schmarvinius
added a commit
that referenced
this pull request
Jun 10, 2026
- Replace resourceGroupForTenant(String) with resourceGroup() on the public AICoreService interface. The implementation reads the tenant from the current RequestContext internally. - Remove isMultiTenancyEnabled() and getRetry() from the public interface; they remain accessible on AbstractAICoreService for internal callers. - Remove the CDS function 'resourceGroupForTenant' from index.cds and its action handler. - Detect multi-tenancy via standard CAP Java cds.multiTenancy.sidecar.url property and DeploymentService presence instead of custom flag. - Update RecommendationClientResolver to drop tenantId parameter. - Update samples, tests, and javadoc accordingly. Addresses review comments from PR #49 (Issue 2).
Schmarvinius
added a commit
that referenced
this pull request
Jun 10, 2026
- Add tenant ownership verification on ResourceGroupHandler for READ (by-key), UPDATE, and DELETE operations. Returns 404 if the resource group belongs to a different tenant. - Scope list queries (READ without key) to the current tenant's resource groups via the tenant label filter in multi-tenancy mode. - Add ensureResourceGroupAccessible() guard to DeploymentHandler and ConfigurationHandler, validating the addressed resource group belongs to the current tenant before forwarding to AI Core. - Provider/system users are exempt from tenant restrictions and can access all resource groups (useful for ops/debug scenarios). - Add isProviderUser() and currentTenantId() as public helpers on AbstractAICoreService for use by handler classes. Addresses review comments from PR #49 (Issue 3a).
Schmarvinius
added a commit
that referenced
this pull request
Jun 10, 2026
- Rename all configuration properties from cds.requires.AICore.* to cds.ai.core.* to align with CAP Java property naming conventions. - Rename cds.requires.recommendations.contextRowLimit to cds.ai.recommendations.contextRowLimit. - Drop the cds.requires.AICore.multiTenancy flag entirely; multi- tenancy is now auto-detected from standard CAP Java properties. - Update README with new configuration namespace and examples. Addresses review comments from PR #49 (Issue 3b).
MattSchur
reviewed
Jun 11, 2026
|
|
||
| private static final long MAX_INTERVAL_MS = 30_000L; | ||
|
|
||
| private static Retry buildRetry(int maxAttempts, long initialDelayMs) { |
| } | ||
|
|
||
| @Override | ||
| public ApiClient inferenceClient(String resourceGroupId, String deploymentId) { |
There was a problem hiding this comment.
Do we want to have an integration point for the ai-sdk-java and other higher-level APIs like LangChain4j? How would this look like?
* Make cache for entitiesWithoutPredictionsPerTenant tenant specific * refactor(ai-core): make AICoreService tenant-agnostic and DI-friendly - Replace resourceGroupForTenant(String) with resourceGroup() on the public AICoreService interface. The implementation reads the tenant from the current RequestContext internally. - Remove isMultiTenancyEnabled() and getRetry() from the public interface; they remain accessible on AbstractAICoreService for internal callers. - Remove the CDS function 'resourceGroupForTenant' from index.cds and its action handler. - Detect multi-tenancy via standard CAP Java cds.multiTenancy.sidecar.url property and DeploymentService presence instead of custom flag. - Update RecommendationClientResolver to drop tenantId parameter. - Update samples, tests, and javadoc accordingly. Addresses review comments from PR #49 (Issue 2). * feat(ai-core): restrict AICore entity APIs to current tenant - Add tenant ownership verification on ResourceGroupHandler for READ (by-key), UPDATE, and DELETE operations. Returns 404 if the resource group belongs to a different tenant. - Scope list queries (READ without key) to the current tenant's resource groups via the tenant label filter in multi-tenancy mode. - Add ensureResourceGroupAccessible() guard to DeploymentHandler and ConfigurationHandler, validating the addressed resource group belongs to the current tenant before forwarding to AI Core. - Provider/system users are exempt from tenant restrictions and can access all resource groups (useful for ops/debug scenarios). - Add isProviderUser() and currentTenantId() as public helpers on AbstractAICoreService for use by handler classes. Addresses review comments from PR #49 (Issue 3a). * chore(ai-core): rename config namespace to cds.ai.core - Rename all configuration properties from cds.requires.AICore.* to cds.ai.core.* to align with CAP Java property naming conventions. - Rename cds.requires.recommendations.contextRowLimit to cds.ai.recommendations.contextRowLimit. - Drop the cds.requires.AICore.multiTenancy flag entirely; multi- tenancy is now auto-detected from standard CAP Java properties. - Update README with new configuration namespace and examples. Addresses review comments from PR #49 (Issue 3b). * fix(ai-core): handle null tenant in resourceGroupForTenant When resourceGroupForTenant is called with a null tenantId (which happens when currentTenantId() returns null in single-tenant or non-tenant-scoped RequestContexts), fall back to the default resource group instead of passing null to the Caffeine cache (which throws NPE). This fixes integration test failures in the CI pipeline where the ApplicationServiceDelegation and Recommendation tests run without an explicit tenant in the RequestContext. * fix(ci): cleanup all run attempts and cds-itest resource groups The cleanup step previously only deleted resource groups matching the exact current run_id AND run_attempt. When a run failed and was re-run, the previous attempt's resource groups were never cleaned up, eventually hitting the AI Core resource group limit (50). Changes: - Match prefix 'itest-{run_id}-' (all attempts) instead of the exact 'itest-{run_id}-{run_attempt}' string. - Same for 'sonar-{run_id}-' prefix. - Also delete 'cds-itest-' prefixed resource groups which are created by the multi-tenancy integration tests via resourceGroupForTenant() and were never cleaned up by the pipeline. * fix(itest): align config namespace with cds.ai.core rename The source code (commit c30080b) renamed properties from cds.requires.AICore.* to cds.ai.core.*, but the integration test application.yaml files were not updated. This meant the CDS_AICORE_TEST_RESOURCE_GROUP env var set by CI was silently ignored and tests always ran against the literal default resource group. - spring/application.yaml: cds.requires.AICore -> cds.ai.core - mtx-local/application.yaml: remove obsolete cds.requires.AICore.multiTenancy (now auto-detected from cds.multi-tenancy.sidecar.url) * test(ai-core): add unit tests for tenant scoping and mock service Cover new code paths introduced by the tenant-scoping branch: - TenantScopingTest (7 tests): exercises every branch of AbstractCrudHandler.ensureResourceGroupAccessible() — provider bypass, single-tenancy bypass, null tenant, matching/non-matching labels, 404. - MockAICoreServiceImplTest (9 tests): both constructors, MT enabled/disabled, resourceGroupForTenant, cache isolation, clearTenantCache, getRetry, config property reads. - AICoreServiceImplDeploymentIdTest (+2 tests): resourceGroupForTenant(null) returns default even with MT enabled; single-tenancy always returns default. * chore(recommendations): add TODO for model-changed integration test Document the missing E2E coverage for RecommendationModelChangedHandler. The proper test requires an extensibility-enabled sidecar with extension JSON that adds prediction columns — not yet set up in mtx-local. The cache-invalidation logic itself is covered by the existing unit test FioriRecommendationHandlerTest.invalidateTenant_removesOnlyThatTenantsEntries. * update cleanup * fix(ci): scope resource group cleanup to own job only Each parallel CI job (Java 17, Java 21, SonarQube) was using broad prefixes in its cleanup step, deleting resource groups belonging to sibling jobs still in progress. This caused intermittent 403 Forbidden errors when the affected jobs tried to use their now-deleted resource groups. Narrow the cleanup prefixes so each job only deletes its own: - integration-tests: itest-{run_id}-{attempt}-j{version}* - scan-with-sonar: sonar-{run_id}-{attempt}* Both still clean up itest-rg-* (ResourceGroupTest leftovers). * test(ai-core): add unit tests for uncovered code paths - DeploymentHandler: test onCreate (with/without TTL) and onUpdate happy path (targetStatus and configurationId branches) - ResourceGroupHandler: test onUpdate with/without labels, buildTenantLabelSelector branches (tenantId filter, MT non-provider, MT null tenant, single tenancy), ensureOwnedByCurrentTenant branches (provider, single tenant, wrong tenant, matching tenant) - AICoreServiceConfiguration: test eventHandlers() MockAICoreServiceImpl branch (with and without multi-tenancy), test detectMultiTenancy via services() for sidecarUrl branch and no-MT fallback * fix(ci): include cds-itest- prefix in resource group cleanup The MultiTenancyTest creates per-tenant resource groups with names like cds-itest-mt-a-{timestamp} (from resourceGroupPrefix 'cds-' + tenant name 'itest-*'). These are unique per test run (timestamped) and safe to clean up from any job without cross-job interference. * refactor: extract inline cleanup script into shared JS file --------- Co-authored-by: Marvin L <marvin.lindner@sap.com>
This was referenced Jun 12, 2026
Schmarvinius
added a commit
that referenced
this pull request
Jun 15, 2026
* Make cache for entitiesWithoutPredictionsPerTenant tenant specific * refactor(ai-core): make AICoreService tenant-agnostic and DI-friendly - Replace resourceGroupForTenant(String) with resourceGroup() on the public AICoreService interface. The implementation reads the tenant from the current RequestContext internally. - Remove isMultiTenancyEnabled() and getRetry() from the public interface; they remain accessible on AbstractAICoreService for internal callers. - Remove the CDS function 'resourceGroupForTenant' from index.cds and its action handler. - Detect multi-tenancy via standard CAP Java cds.multiTenancy.sidecar.url property and DeploymentService presence instead of custom flag. - Update RecommendationClientResolver to drop tenantId parameter. - Update samples, tests, and javadoc accordingly. Addresses review comments from PR #49 (Issue 2). * feat(ai-core): restrict AICore entity APIs to current tenant - Add tenant ownership verification on ResourceGroupHandler for READ (by-key), UPDATE, and DELETE operations. Returns 404 if the resource group belongs to a different tenant. - Scope list queries (READ without key) to the current tenant's resource groups via the tenant label filter in multi-tenancy mode. - Add ensureResourceGroupAccessible() guard to DeploymentHandler and ConfigurationHandler, validating the addressed resource group belongs to the current tenant before forwarding to AI Core. - Provider/system users are exempt from tenant restrictions and can access all resource groups (useful for ops/debug scenarios). - Add isProviderUser() and currentTenantId() as public helpers on AbstractAICoreService for use by handler classes. Addresses review comments from PR #49 (Issue 3a). * chore(ai-core): rename config namespace to cds.ai.core - Rename all configuration properties from cds.requires.AICore.* to cds.ai.core.* to align with CAP Java property naming conventions. - Rename cds.requires.recommendations.contextRowLimit to cds.ai.recommendations.contextRowLimit. - Drop the cds.requires.AICore.multiTenancy flag entirely; multi- tenancy is now auto-detected from standard CAP Java properties. - Update README with new configuration namespace and examples. Addresses review comments from PR #49 (Issue 3b). * fix(ai-core): handle null tenant in resourceGroupForTenant When resourceGroupForTenant is called with a null tenantId (which happens when currentTenantId() returns null in single-tenant or non-tenant-scoped RequestContexts), fall back to the default resource group instead of passing null to the Caffeine cache (which throws NPE). This fixes integration test failures in the CI pipeline where the ApplicationServiceDelegation and Recommendation tests run without an explicit tenant in the RequestContext. * fix(ci): cleanup all run attempts and cds-itest resource groups The cleanup step previously only deleted resource groups matching the exact current run_id AND run_attempt. When a run failed and was re-run, the previous attempt's resource groups were never cleaned up, eventually hitting the AI Core resource group limit (50). Changes: - Match prefix 'itest-{run_id}-' (all attempts) instead of the exact 'itest-{run_id}-{run_attempt}' string. - Same for 'sonar-{run_id}-' prefix. - Also delete 'cds-itest-' prefixed resource groups which are created by the multi-tenancy integration tests via resourceGroupForTenant() and were never cleaned up by the pipeline. * fix(itest): align config namespace with cds.ai.core rename The source code (commit c30080b) renamed properties from cds.requires.AICore.* to cds.ai.core.*, but the integration test application.yaml files were not updated. This meant the CDS_AICORE_TEST_RESOURCE_GROUP env var set by CI was silently ignored and tests always ran against the literal default resource group. - spring/application.yaml: cds.requires.AICore -> cds.ai.core - mtx-local/application.yaml: remove obsolete cds.requires.AICore.multiTenancy (now auto-detected from cds.multi-tenancy.sidecar.url) * test(ai-core): add unit tests for tenant scoping and mock service Cover new code paths introduced by the tenant-scoping branch: - TenantScopingTest (7 tests): exercises every branch of AbstractCrudHandler.ensureResourceGroupAccessible() — provider bypass, single-tenancy bypass, null tenant, matching/non-matching labels, 404. - MockAICoreServiceImplTest (9 tests): both constructors, MT enabled/disabled, resourceGroupForTenant, cache isolation, clearTenantCache, getRetry, config property reads. - AICoreServiceImplDeploymentIdTest (+2 tests): resourceGroupForTenant(null) returns default even with MT enabled; single-tenancy always returns default. * chore(recommendations): add TODO for model-changed integration test Document the missing E2E coverage for RecommendationModelChangedHandler. The proper test requires an extensibility-enabled sidecar with extension JSON that adds prediction columns — not yet set up in mtx-local. The cache-invalidation logic itself is covered by the existing unit test FioriRecommendationHandlerTest.invalidateTenant_removesOnlyThatTenantsEntries. * update cleanup * fix(ci): scope resource group cleanup to own job only Each parallel CI job (Java 17, Java 21, SonarQube) was using broad prefixes in its cleanup step, deleting resource groups belonging to sibling jobs still in progress. This caused intermittent 403 Forbidden errors when the affected jobs tried to use their now-deleted resource groups. Narrow the cleanup prefixes so each job only deletes its own: - integration-tests: itest-{run_id}-{attempt}-j{version}* - scan-with-sonar: sonar-{run_id}-{attempt}* Both still clean up itest-rg-* (ResourceGroupTest leftovers). * test(ai-core): add unit tests for uncovered code paths - DeploymentHandler: test onCreate (with/without TTL) and onUpdate happy path (targetStatus and configurationId branches) - ResourceGroupHandler: test onUpdate with/without labels, buildTenantLabelSelector branches (tenantId filter, MT non-provider, MT null tenant, single tenancy), ensureOwnedByCurrentTenant branches (provider, single tenant, wrong tenant, matching tenant) - AICoreServiceConfiguration: test eventHandlers() MockAICoreServiceImpl branch (with and without multi-tenancy), test detectMultiTenancy via services() for sidecarUrl branch and no-MT fallback * fix(ci): include cds-itest- prefix in resource group cleanup The MultiTenancyTest creates per-tenant resource groups with names like cds-itest-mt-a-{timestamp} (from resourceGroupPrefix 'cds-' + tenant name 'itest-*'). These are unique per test run (timestamped) and safe to clean up from any job without cross-job interference. * refactor(ai-core): migrate AICoreService from CqnService to RemoteService * refactor(ai-core): delete AICoreApplicationServiceHandler * fix(ai-core): guard service registration on AICore model presence * fix(test): mock CdsModel in unit tests and restore AICore model import * fix(ai-core): extend AbstractCdsDefinedService for proper RemoteService support * fix(recommendations): promote cds-services-impl to compile scope * refactor(test): use real CdsRuntime in AICoreServiceConfigurationTest * refactor(ai-core): remove AICORE_SERVICE_KEY env var check from binding detection * chore: exclude Mock* classes from SonarQube coverage * refactor(test): use real CdsRuntime in AICoreServiceImplDeploymentIdTest * fix: remove duplicate detectMultiTenancy method from merge --------- Co-authored-by: Lisa Julia Nebel <lisa.nebel@sap.com>
Schmarvinius
added a commit
that referenced
this pull request
Jun 15, 2026
* Make cache for entitiesWithoutPredictionsPerTenant tenant specific * refactor(ai-core): make AICoreService tenant-agnostic and DI-friendly - Replace resourceGroupForTenant(String) with resourceGroup() on the public AICoreService interface. The implementation reads the tenant from the current RequestContext internally. - Remove isMultiTenancyEnabled() and getRetry() from the public interface; they remain accessible on AbstractAICoreService for internal callers. - Remove the CDS function 'resourceGroupForTenant' from index.cds and its action handler. - Detect multi-tenancy via standard CAP Java cds.multiTenancy.sidecar.url property and DeploymentService presence instead of custom flag. - Update RecommendationClientResolver to drop tenantId parameter. - Update samples, tests, and javadoc accordingly. Addresses review comments from PR #49 (Issue 2). * feat(ai-core): restrict AICore entity APIs to current tenant - Add tenant ownership verification on ResourceGroupHandler for READ (by-key), UPDATE, and DELETE operations. Returns 404 if the resource group belongs to a different tenant. - Scope list queries (READ without key) to the current tenant's resource groups via the tenant label filter in multi-tenancy mode. - Add ensureResourceGroupAccessible() guard to DeploymentHandler and ConfigurationHandler, validating the addressed resource group belongs to the current tenant before forwarding to AI Core. - Provider/system users are exempt from tenant restrictions and can access all resource groups (useful for ops/debug scenarios). - Add isProviderUser() and currentTenantId() as public helpers on AbstractAICoreService for use by handler classes. Addresses review comments from PR #49 (Issue 3a). * chore(ai-core): rename config namespace to cds.ai.core - Rename all configuration properties from cds.requires.AICore.* to cds.ai.core.* to align with CAP Java property naming conventions. - Rename cds.requires.recommendations.contextRowLimit to cds.ai.recommendations.contextRowLimit. - Drop the cds.requires.AICore.multiTenancy flag entirely; multi- tenancy is now auto-detected from standard CAP Java properties. - Update README with new configuration namespace and examples. Addresses review comments from PR #49 (Issue 3b). * fix(ai-core): handle null tenant in resourceGroupForTenant When resourceGroupForTenant is called with a null tenantId (which happens when currentTenantId() returns null in single-tenant or non-tenant-scoped RequestContexts), fall back to the default resource group instead of passing null to the Caffeine cache (which throws NPE). This fixes integration test failures in the CI pipeline where the ApplicationServiceDelegation and Recommendation tests run without an explicit tenant in the RequestContext. * fix(ci): cleanup all run attempts and cds-itest resource groups The cleanup step previously only deleted resource groups matching the exact current run_id AND run_attempt. When a run failed and was re-run, the previous attempt's resource groups were never cleaned up, eventually hitting the AI Core resource group limit (50). Changes: - Match prefix 'itest-{run_id}-' (all attempts) instead of the exact 'itest-{run_id}-{run_attempt}' string. - Same for 'sonar-{run_id}-' prefix. - Also delete 'cds-itest-' prefixed resource groups which are created by the multi-tenancy integration tests via resourceGroupForTenant() and were never cleaned up by the pipeline. * fix(itest): align config namespace with cds.ai.core rename The source code (commit c30080b) renamed properties from cds.requires.AICore.* to cds.ai.core.*, but the integration test application.yaml files were not updated. This meant the CDS_AICORE_TEST_RESOURCE_GROUP env var set by CI was silently ignored and tests always ran against the literal default resource group. - spring/application.yaml: cds.requires.AICore -> cds.ai.core - mtx-local/application.yaml: remove obsolete cds.requires.AICore.multiTenancy (now auto-detected from cds.multi-tenancy.sidecar.url) * test(ai-core): add unit tests for tenant scoping and mock service Cover new code paths introduced by the tenant-scoping branch: - TenantScopingTest (7 tests): exercises every branch of AbstractCrudHandler.ensureResourceGroupAccessible() — provider bypass, single-tenancy bypass, null tenant, matching/non-matching labels, 404. - MockAICoreServiceImplTest (9 tests): both constructors, MT enabled/disabled, resourceGroupForTenant, cache isolation, clearTenantCache, getRetry, config property reads. - AICoreServiceImplDeploymentIdTest (+2 tests): resourceGroupForTenant(null) returns default even with MT enabled; single-tenancy always returns default. * chore(recommendations): add TODO for model-changed integration test Document the missing E2E coverage for RecommendationModelChangedHandler. The proper test requires an extensibility-enabled sidecar with extension JSON that adds prediction columns — not yet set up in mtx-local. The cache-invalidation logic itself is covered by the existing unit test FioriRecommendationHandlerTest.invalidateTenant_removesOnlyThatTenantsEntries. * update cleanup * fix(ci): scope resource group cleanup to own job only Each parallel CI job (Java 17, Java 21, SonarQube) was using broad prefixes in its cleanup step, deleting resource groups belonging to sibling jobs still in progress. This caused intermittent 403 Forbidden errors when the affected jobs tried to use their now-deleted resource groups. Narrow the cleanup prefixes so each job only deletes its own: - integration-tests: itest-{run_id}-{attempt}-j{version}* - scan-with-sonar: sonar-{run_id}-{attempt}* Both still clean up itest-rg-* (ResourceGroupTest leftovers). * test(ai-core): add unit tests for uncovered code paths - DeploymentHandler: test onCreate (with/without TTL) and onUpdate happy path (targetStatus and configurationId branches) - ResourceGroupHandler: test onUpdate with/without labels, buildTenantLabelSelector branches (tenantId filter, MT non-provider, MT null tenant, single tenancy), ensureOwnedByCurrentTenant branches (provider, single tenant, wrong tenant, matching tenant) - AICoreServiceConfiguration: test eventHandlers() MockAICoreServiceImpl branch (with and without multi-tenancy), test detectMultiTenancy via services() for sidecarUrl branch and no-MT fallback * fix(ci): include cds-itest- prefix in resource group cleanup The MultiTenancyTest creates per-tenant resource groups with names like cds-itest-mt-a-{timestamp} (from resourceGroupPrefix 'cds-' + tenant name 'itest-*'). These are unique per test run (timestamped) and safe to clean up from any job without cross-job interference. * refactor(ai-core): migrate AICoreService from CqnService to RemoteService * refactor(ai-core): delete AICoreApplicationServiceHandler * fix(ai-core): guard service registration on AICore model presence * fix(test): mock CdsModel in unit tests and restore AICore model import * fix(ai-core): extend AbstractCdsDefinedService for proper RemoteService support * fix(recommendations): promote cds-services-impl to compile scope * refactor(test): use real CdsRuntime in AICoreServiceConfigurationTest * refactor(ai-core): remove AICORE_SERVICE_KEY env var check from binding detection * chore: exclude Mock* classes from SonarQube coverage * refactor(test): use real CdsRuntime in AICoreServiceImplDeploymentIdTest * fix: remove duplicate detectMultiTenancy method from merge * refactor(ai-core): rename DEFAULT_NAME to AICoreService$Default Follow standard CAP Java naming convention for service instances (ServiceInterface$Default). The CDS definition name stays 'AICore' (matching the CDS model); only the registered instance name changes. * refactor(ai-core): define EventContext subinterfaces for programmatic API Add typed EventContext interfaces in the api package for the three programmatic API methods: - DeploymentIdContext: for deploymentId(resourceGroupId, spec) - InferenceClientContext: for inferenceClient(resourceGroupId, deploymentId) - ResourceGroupContext: for resourceGroup() These enable the idiomatic CAP pattern where service methods emit events and ON handlers provide the implementation, allowing extensibility via @Before/@after hooks. * refactor(ai-core): make service API methods emit events; move logic to handler Apply idiomatic CAP Java pattern: service methods create typed EventContext, emit(), and return result. A separately-registered AICoreApiHandler provides the ON implementation with the actual business logic. - AICoreServiceImpl.deploymentId/inferenceClient/resourceGroup/ resourceGroupForTenant now emit typed contexts instead of doing work directly - New AICoreApiHandler handles DeploymentIdContext, InferenceClientContext, ResourceGroupContext with all caching, retry, and SDK logic - ResourceGroupContext extended with optional tenantId for explicit-tenant path - AICoreServiceConfiguration registers AICoreApiHandler - AICoreServiceImpl retains shared state (caches, config, APIs) accessed by handlers via EventContext.getService() This enables extensibility: apps can register @Before/@after handlers on deploymentId, inferenceClient, and resourceGroup events. * refactor(ai-core): decouple CRUD handlers from service impl; use typed contexts - Remove AICoreServiceImpl field from AbstractCrudHandler; handlers now obtain the service from EventContext.getService() at invocation time. - Pass SDK API clients (DeploymentApi, ResourceGroupApi, ConfigurationApi) directly via constructor injection — stateless clients don't need the service reference. - ActionHandler uses generated DeploymentsStopContext instead of raw EventContext with string-based key extraction. - All handlers use generated entity name constants (Deployments_.CDS_NAME, ResourceGroups_.CDS_NAME, Configurations_.CDS_NAME) instead of hand-written strings. - Update AICoreServiceConfiguration to pass API clients to handler constructors. - Update all handler unit tests for the new constructor signatures and EventContext-based service access pattern. Addresses issue #70: typesafe handlers decoupled from service impl. * refactor(ai-core): remove redundant event params from handler annotations * test(ai-core): rewrite handler tests to use real CdsRuntime Replace heavily-mocked unit tests with integration-style tests that boot a real CdsRuntime, register real handlers, and dispatch CQN through the full handler pipeline. Only SDK API clients remain mocked. - DeploymentHandlerTest: tests CREATE, UPDATE via service.run() - ConfigurationHandlerTest: tests READ, CREATE via service.run() - ResourceGroupHandlerTest: tests CRUD + MT label filtering - TenantScopingTest: tests tenant isolation through actual CQN operations with different RequestContext tenants * refactor(ai-core): extract AICoreConfig and AICoreClients Immutable record for config values and holder for SDK API clients. * refactor(ai-core): extract DeploymentResolver Encapsulates caches, locks, retry, and validation behind resolveResourceGroup, resolveDeployment, invalidateTenant. * refactor(ai-core): inject components into handlers Handlers receive dependencies via constructor. Use context.getUserInfo() for tenant/provider checks directly. * refactor(ai-core): slim AICoreServiceImpl to pure delegation Zero fields, zero accessors. Delete AbstractAICoreService and MockAICoreServiceImpl. Add resourceGroupForTenant to interface. * refactor(ai-core): rewire configuration and setup handlers Configuration creates AICoreConfig, AICoreClients, DeploymentResolver and injects them into handlers at registration time. * refactor(recommendations): RptInferenceClient owns its retry Single-arg constructor, no dependency on service internals. Remove AbstractAICoreService casts from all consumers. * test(ai-core): update tests for new component architecture Adapt all unit and integration tests to use AICoreConfig, AICoreClients, DeploymentResolver instead of service accessors. * for pipeline * fix(ai-core): separate retry boundary to prevent orphaned deployments Only retry the deployment creation call. Polling is handled separately so a poll timeout does not re-create deployments. * refactor(ai-core): remove all service references from handlers Handlers use DeploymentResolver.resolveResourceGroup() directly instead of casting context.getService(). Zero service references. * fix(ai-core): add handler ordering and wire mock cleanup Add @HandlerOrder to setup handlers for DeploymentService events. Wire MockAICoreSetupHandler to actually call clearTenantCache(). Use ServiceException in mock inference handler. * fix(ai-core): validate config at startup, document impl coupling Fail fast on invalid cds.ai.core.* property values. Document AbstractCdsDefinedService dependency rationale. * test(ai-core): update tests for DeploymentResolver expansion Pass ResourceGroupApi to DeploymentResolver constructor. Pass DeploymentResolver to CRUD handler constructors. --------- Co-authored-by: Lisa Julia Nebel <lisa.nebel@sap.com>
* pipeline * refactor(ai-core): migrate AICoreService to RemoteService (#73) * Make cache for entitiesWithoutPredictionsPerTenant tenant specific * refactor(ai-core): make AICoreService tenant-agnostic and DI-friendly - Replace resourceGroupForTenant(String) with resourceGroup() on the public AICoreService interface. The implementation reads the tenant from the current RequestContext internally. - Remove isMultiTenancyEnabled() and getRetry() from the public interface; they remain accessible on AbstractAICoreService for internal callers. - Remove the CDS function 'resourceGroupForTenant' from index.cds and its action handler. - Detect multi-tenancy via standard CAP Java cds.multiTenancy.sidecar.url property and DeploymentService presence instead of custom flag. - Update RecommendationClientResolver to drop tenantId parameter. - Update samples, tests, and javadoc accordingly. Addresses review comments from PR #49 (Issue 2). * feat(ai-core): restrict AICore entity APIs to current tenant - Add tenant ownership verification on ResourceGroupHandler for READ (by-key), UPDATE, and DELETE operations. Returns 404 if the resource group belongs to a different tenant. - Scope list queries (READ without key) to the current tenant's resource groups via the tenant label filter in multi-tenancy mode. - Add ensureResourceGroupAccessible() guard to DeploymentHandler and ConfigurationHandler, validating the addressed resource group belongs to the current tenant before forwarding to AI Core. - Provider/system users are exempt from tenant restrictions and can access all resource groups (useful for ops/debug scenarios). - Add isProviderUser() and currentTenantId() as public helpers on AbstractAICoreService for use by handler classes. Addresses review comments from PR #49 (Issue 3a). * chore(ai-core): rename config namespace to cds.ai.core - Rename all configuration properties from cds.requires.AICore.* to cds.ai.core.* to align with CAP Java property naming conventions. - Rename cds.requires.recommendations.contextRowLimit to cds.ai.recommendations.contextRowLimit. - Drop the cds.requires.AICore.multiTenancy flag entirely; multi- tenancy is now auto-detected from standard CAP Java properties. - Update README with new configuration namespace and examples. Addresses review comments from PR #49 (Issue 3b). * fix(ai-core): handle null tenant in resourceGroupForTenant When resourceGroupForTenant is called with a null tenantId (which happens when currentTenantId() returns null in single-tenant or non-tenant-scoped RequestContexts), fall back to the default resource group instead of passing null to the Caffeine cache (which throws NPE). This fixes integration test failures in the CI pipeline where the ApplicationServiceDelegation and Recommendation tests run without an explicit tenant in the RequestContext. * fix(ci): cleanup all run attempts and cds-itest resource groups The cleanup step previously only deleted resource groups matching the exact current run_id AND run_attempt. When a run failed and was re-run, the previous attempt's resource groups were never cleaned up, eventually hitting the AI Core resource group limit (50). Changes: - Match prefix 'itest-{run_id}-' (all attempts) instead of the exact 'itest-{run_id}-{run_attempt}' string. - Same for 'sonar-{run_id}-' prefix. - Also delete 'cds-itest-' prefixed resource groups which are created by the multi-tenancy integration tests via resourceGroupForTenant() and were never cleaned up by the pipeline. * fix(itest): align config namespace with cds.ai.core rename The source code (commit c30080b) renamed properties from cds.requires.AICore.* to cds.ai.core.*, but the integration test application.yaml files were not updated. This meant the CDS_AICORE_TEST_RESOURCE_GROUP env var set by CI was silently ignored and tests always ran against the literal default resource group. - spring/application.yaml: cds.requires.AICore -> cds.ai.core - mtx-local/application.yaml: remove obsolete cds.requires.AICore.multiTenancy (now auto-detected from cds.multi-tenancy.sidecar.url) * test(ai-core): add unit tests for tenant scoping and mock service Cover new code paths introduced by the tenant-scoping branch: - TenantScopingTest (7 tests): exercises every branch of AbstractCrudHandler.ensureResourceGroupAccessible() — provider bypass, single-tenancy bypass, null tenant, matching/non-matching labels, 404. - MockAICoreServiceImplTest (9 tests): both constructors, MT enabled/disabled, resourceGroupForTenant, cache isolation, clearTenantCache, getRetry, config property reads. - AICoreServiceImplDeploymentIdTest (+2 tests): resourceGroupForTenant(null) returns default even with MT enabled; single-tenancy always returns default. * chore(recommendations): add TODO for model-changed integration test Document the missing E2E coverage for RecommendationModelChangedHandler. The proper test requires an extensibility-enabled sidecar with extension JSON that adds prediction columns — not yet set up in mtx-local. The cache-invalidation logic itself is covered by the existing unit test FioriRecommendationHandlerTest.invalidateTenant_removesOnlyThatTenantsEntries. * update cleanup * fix(ci): scope resource group cleanup to own job only Each parallel CI job (Java 17, Java 21, SonarQube) was using broad prefixes in its cleanup step, deleting resource groups belonging to sibling jobs still in progress. This caused intermittent 403 Forbidden errors when the affected jobs tried to use their now-deleted resource groups. Narrow the cleanup prefixes so each job only deletes its own: - integration-tests: itest-{run_id}-{attempt}-j{version}* - scan-with-sonar: sonar-{run_id}-{attempt}* Both still clean up itest-rg-* (ResourceGroupTest leftovers). * test(ai-core): add unit tests for uncovered code paths - DeploymentHandler: test onCreate (with/without TTL) and onUpdate happy path (targetStatus and configurationId branches) - ResourceGroupHandler: test onUpdate with/without labels, buildTenantLabelSelector branches (tenantId filter, MT non-provider, MT null tenant, single tenancy), ensureOwnedByCurrentTenant branches (provider, single tenant, wrong tenant, matching tenant) - AICoreServiceConfiguration: test eventHandlers() MockAICoreServiceImpl branch (with and without multi-tenancy), test detectMultiTenancy via services() for sidecarUrl branch and no-MT fallback * fix(ci): include cds-itest- prefix in resource group cleanup The MultiTenancyTest creates per-tenant resource groups with names like cds-itest-mt-a-{timestamp} (from resourceGroupPrefix 'cds-' + tenant name 'itest-*'). These are unique per test run (timestamped) and safe to clean up from any job without cross-job interference. * refactor(ai-core): migrate AICoreService from CqnService to RemoteService * refactor(ai-core): delete AICoreApplicationServiceHandler * fix(ai-core): guard service registration on AICore model presence * fix(test): mock CdsModel in unit tests and restore AICore model import * fix(ai-core): extend AbstractCdsDefinedService for proper RemoteService support * fix(recommendations): promote cds-services-impl to compile scope * refactor(test): use real CdsRuntime in AICoreServiceConfigurationTest * refactor(ai-core): remove AICORE_SERVICE_KEY env var check from binding detection * chore: exclude Mock* classes from SonarQube coverage * refactor(test): use real CdsRuntime in AICoreServiceImplDeploymentIdTest * fix: remove duplicate detectMultiTenancy method from merge --------- Co-authored-by: Lisa Julia Nebel <lisa.nebel@sap.com> * refactor(ai-core): typesafe handlers decoupled from service impl (#74) * Make cache for entitiesWithoutPredictionsPerTenant tenant specific * refactor(ai-core): make AICoreService tenant-agnostic and DI-friendly - Replace resourceGroupForTenant(String) with resourceGroup() on the public AICoreService interface. The implementation reads the tenant from the current RequestContext internally. - Remove isMultiTenancyEnabled() and getRetry() from the public interface; they remain accessible on AbstractAICoreService for internal callers. - Remove the CDS function 'resourceGroupForTenant' from index.cds and its action handler. - Detect multi-tenancy via standard CAP Java cds.multiTenancy.sidecar.url property and DeploymentService presence instead of custom flag. - Update RecommendationClientResolver to drop tenantId parameter. - Update samples, tests, and javadoc accordingly. Addresses review comments from PR #49 (Issue 2). * feat(ai-core): restrict AICore entity APIs to current tenant - Add tenant ownership verification on ResourceGroupHandler for READ (by-key), UPDATE, and DELETE operations. Returns 404 if the resource group belongs to a different tenant. - Scope list queries (READ without key) to the current tenant's resource groups via the tenant label filter in multi-tenancy mode. - Add ensureResourceGroupAccessible() guard to DeploymentHandler and ConfigurationHandler, validating the addressed resource group belongs to the current tenant before forwarding to AI Core. - Provider/system users are exempt from tenant restrictions and can access all resource groups (useful for ops/debug scenarios). - Add isProviderUser() and currentTenantId() as public helpers on AbstractAICoreService for use by handler classes. Addresses review comments from PR #49 (Issue 3a). * chore(ai-core): rename config namespace to cds.ai.core - Rename all configuration properties from cds.requires.AICore.* to cds.ai.core.* to align with CAP Java property naming conventions. - Rename cds.requires.recommendations.contextRowLimit to cds.ai.recommendations.contextRowLimit. - Drop the cds.requires.AICore.multiTenancy flag entirely; multi- tenancy is now auto-detected from standard CAP Java properties. - Update README with new configuration namespace and examples. Addresses review comments from PR #49 (Issue 3b). * fix(ai-core): handle null tenant in resourceGroupForTenant When resourceGroupForTenant is called with a null tenantId (which happens when currentTenantId() returns null in single-tenant or non-tenant-scoped RequestContexts), fall back to the default resource group instead of passing null to the Caffeine cache (which throws NPE). This fixes integration test failures in the CI pipeline where the ApplicationServiceDelegation and Recommendation tests run without an explicit tenant in the RequestContext. * fix(ci): cleanup all run attempts and cds-itest resource groups The cleanup step previously only deleted resource groups matching the exact current run_id AND run_attempt. When a run failed and was re-run, the previous attempt's resource groups were never cleaned up, eventually hitting the AI Core resource group limit (50). Changes: - Match prefix 'itest-{run_id}-' (all attempts) instead of the exact 'itest-{run_id}-{run_attempt}' string. - Same for 'sonar-{run_id}-' prefix. - Also delete 'cds-itest-' prefixed resource groups which are created by the multi-tenancy integration tests via resourceGroupForTenant() and were never cleaned up by the pipeline. * fix(itest): align config namespace with cds.ai.core rename The source code (commit c30080b) renamed properties from cds.requires.AICore.* to cds.ai.core.*, but the integration test application.yaml files were not updated. This meant the CDS_AICORE_TEST_RESOURCE_GROUP env var set by CI was silently ignored and tests always ran against the literal default resource group. - spring/application.yaml: cds.requires.AICore -> cds.ai.core - mtx-local/application.yaml: remove obsolete cds.requires.AICore.multiTenancy (now auto-detected from cds.multi-tenancy.sidecar.url) * test(ai-core): add unit tests for tenant scoping and mock service Cover new code paths introduced by the tenant-scoping branch: - TenantScopingTest (7 tests): exercises every branch of AbstractCrudHandler.ensureResourceGroupAccessible() — provider bypass, single-tenancy bypass, null tenant, matching/non-matching labels, 404. - MockAICoreServiceImplTest (9 tests): both constructors, MT enabled/disabled, resourceGroupForTenant, cache isolation, clearTenantCache, getRetry, config property reads. - AICoreServiceImplDeploymentIdTest (+2 tests): resourceGroupForTenant(null) returns default even with MT enabled; single-tenancy always returns default. * chore(recommendations): add TODO for model-changed integration test Document the missing E2E coverage for RecommendationModelChangedHandler. The proper test requires an extensibility-enabled sidecar with extension JSON that adds prediction columns — not yet set up in mtx-local. The cache-invalidation logic itself is covered by the existing unit test FioriRecommendationHandlerTest.invalidateTenant_removesOnlyThatTenantsEntries. * update cleanup * fix(ci): scope resource group cleanup to own job only Each parallel CI job (Java 17, Java 21, SonarQube) was using broad prefixes in its cleanup step, deleting resource groups belonging to sibling jobs still in progress. This caused intermittent 403 Forbidden errors when the affected jobs tried to use their now-deleted resource groups. Narrow the cleanup prefixes so each job only deletes its own: - integration-tests: itest-{run_id}-{attempt}-j{version}* - scan-with-sonar: sonar-{run_id}-{attempt}* Both still clean up itest-rg-* (ResourceGroupTest leftovers). * test(ai-core): add unit tests for uncovered code paths - DeploymentHandler: test onCreate (with/without TTL) and onUpdate happy path (targetStatus and configurationId branches) - ResourceGroupHandler: test onUpdate with/without labels, buildTenantLabelSelector branches (tenantId filter, MT non-provider, MT null tenant, single tenancy), ensureOwnedByCurrentTenant branches (provider, single tenant, wrong tenant, matching tenant) - AICoreServiceConfiguration: test eventHandlers() MockAICoreServiceImpl branch (with and without multi-tenancy), test detectMultiTenancy via services() for sidecarUrl branch and no-MT fallback * fix(ci): include cds-itest- prefix in resource group cleanup The MultiTenancyTest creates per-tenant resource groups with names like cds-itest-mt-a-{timestamp} (from resourceGroupPrefix 'cds-' + tenant name 'itest-*'). These are unique per test run (timestamped) and safe to clean up from any job without cross-job interference. * refactor(ai-core): migrate AICoreService from CqnService to RemoteService * refactor(ai-core): delete AICoreApplicationServiceHandler * fix(ai-core): guard service registration on AICore model presence * fix(test): mock CdsModel in unit tests and restore AICore model import * fix(ai-core): extend AbstractCdsDefinedService for proper RemoteService support * fix(recommendations): promote cds-services-impl to compile scope * refactor(test): use real CdsRuntime in AICoreServiceConfigurationTest * refactor(ai-core): remove AICORE_SERVICE_KEY env var check from binding detection * chore: exclude Mock* classes from SonarQube coverage * refactor(test): use real CdsRuntime in AICoreServiceImplDeploymentIdTest * fix: remove duplicate detectMultiTenancy method from merge * refactor(ai-core): rename DEFAULT_NAME to AICoreService$Default Follow standard CAP Java naming convention for service instances (ServiceInterface$Default). The CDS definition name stays 'AICore' (matching the CDS model); only the registered instance name changes. * refactor(ai-core): define EventContext subinterfaces for programmatic API Add typed EventContext interfaces in the api package for the three programmatic API methods: - DeploymentIdContext: for deploymentId(resourceGroupId, spec) - InferenceClientContext: for inferenceClient(resourceGroupId, deploymentId) - ResourceGroupContext: for resourceGroup() These enable the idiomatic CAP pattern where service methods emit events and ON handlers provide the implementation, allowing extensibility via @Before/@after hooks. * refactor(ai-core): make service API methods emit events; move logic to handler Apply idiomatic CAP Java pattern: service methods create typed EventContext, emit(), and return result. A separately-registered AICoreApiHandler provides the ON implementation with the actual business logic. - AICoreServiceImpl.deploymentId/inferenceClient/resourceGroup/ resourceGroupForTenant now emit typed contexts instead of doing work directly - New AICoreApiHandler handles DeploymentIdContext, InferenceClientContext, ResourceGroupContext with all caching, retry, and SDK logic - ResourceGroupContext extended with optional tenantId for explicit-tenant path - AICoreServiceConfiguration registers AICoreApiHandler - AICoreServiceImpl retains shared state (caches, config, APIs) accessed by handlers via EventContext.getService() This enables extensibility: apps can register @Before/@after handlers on deploymentId, inferenceClient, and resourceGroup events. * refactor(ai-core): decouple CRUD handlers from service impl; use typed contexts - Remove AICoreServiceImpl field from AbstractCrudHandler; handlers now obtain the service from EventContext.getService() at invocation time. - Pass SDK API clients (DeploymentApi, ResourceGroupApi, ConfigurationApi) directly via constructor injection — stateless clients don't need the service reference. - ActionHandler uses generated DeploymentsStopContext instead of raw EventContext with string-based key extraction. - All handlers use generated entity name constants (Deployments_.CDS_NAME, ResourceGroups_.CDS_NAME, Configurations_.CDS_NAME) instead of hand-written strings. - Update AICoreServiceConfiguration to pass API clients to handler constructors. - Update all handler unit tests for the new constructor signatures and EventContext-based service access pattern. Addresses issue #70: typesafe handlers decoupled from service impl. * refactor(ai-core): remove redundant event params from handler annotations * test(ai-core): rewrite handler tests to use real CdsRuntime Replace heavily-mocked unit tests with integration-style tests that boot a real CdsRuntime, register real handlers, and dispatch CQN through the full handler pipeline. Only SDK API clients remain mocked. - DeploymentHandlerTest: tests CREATE, UPDATE via service.run() - ConfigurationHandlerTest: tests READ, CREATE via service.run() - ResourceGroupHandlerTest: tests CRUD + MT label filtering - TenantScopingTest: tests tenant isolation through actual CQN operations with different RequestContext tenants * refactor(ai-core): extract AICoreConfig and AICoreClients Immutable record for config values and holder for SDK API clients. * refactor(ai-core): extract DeploymentResolver Encapsulates caches, locks, retry, and validation behind resolveResourceGroup, resolveDeployment, invalidateTenant. * refactor(ai-core): inject components into handlers Handlers receive dependencies via constructor. Use context.getUserInfo() for tenant/provider checks directly. * refactor(ai-core): slim AICoreServiceImpl to pure delegation Zero fields, zero accessors. Delete AbstractAICoreService and MockAICoreServiceImpl. Add resourceGroupForTenant to interface. * refactor(ai-core): rewire configuration and setup handlers Configuration creates AICoreConfig, AICoreClients, DeploymentResolver and injects them into handlers at registration time. * refactor(recommendations): RptInferenceClient owns its retry Single-arg constructor, no dependency on service internals. Remove AbstractAICoreService casts from all consumers. * test(ai-core): update tests for new component architecture Adapt all unit and integration tests to use AICoreConfig, AICoreClients, DeploymentResolver instead of service accessors. * for pipeline * fix(ai-core): separate retry boundary to prevent orphaned deployments Only retry the deployment creation call. Polling is handled separately so a poll timeout does not re-create deployments. * refactor(ai-core): remove all service references from handlers Handlers use DeploymentResolver.resolveResourceGroup() directly instead of casting context.getService(). Zero service references. * fix(ai-core): add handler ordering and wire mock cleanup Add @HandlerOrder to setup handlers for DeploymentService events. Wire MockAICoreSetupHandler to actually call clearTenantCache(). Use ServiceException in mock inference handler. * fix(ai-core): validate config at startup, document impl coupling Fail fast on invalid cds.ai.core.* property values. Document AbstractCdsDefinedService dependency rationale. * test(ai-core): update tests for DeploymentResolver expansion Pass ResourceGroupApi to DeploymentResolver constructor. Pass DeploymentResolver to CRUD handler constructors. --------- Co-authored-by: Lisa Julia Nebel <lisa.nebel@sap.com> * chore(ai-core): cleanup (#76) * move handler to proper package * cleanup not relevant params in Before/After annotation * fix local test * Refactor cds-feature-recommendations (#77) * Separate the predictionRow from the contextRows and add a constant for [PREDICT] * Add comment on why we the @FunctionalInterface annotation is useful * Add comment to FioriRecommendationHandler * Moved everthing RPT-1 specific into RptInferenceClient * Rename RptInferenceClient.api -> RptInferenceClient.rpt and extract code that creates sdkRow to separate method * Moved everthing RPT-1 specific into RptInferenceClient * Replace MANAGED_FIELDS set with annotation-driven exclusion: @Core.Computed, @readonly and remove logic from 'toSdkRow' method that was already executed elsewhere * Change level of log statement when no suitable context columns are found and recommendations are therefore skipped * Move 'return early if predictRow == null' to earlier in afterRead of FioriRecommendationHandler * Extract 'SAP_Recommendations' into a constant * Get Persistence Service db via Dependency Injection * Move missingPredictionElementNames to earlier in the afterRead of the FioriRecommendationHandler * Add comment about conversion from List<Row> to List<CdsData> * Change check for active Entity: only return early if isActiveEntity is selected and false * Add comments to MockRecommendationClient * Change type of slectcolumns to Set * Add test: row for which we want to do predictions is automatically excluded in the select query returned by buildContextQuery * Add comment about ordering in the select query returned by buildContextQuery * Add comment computeSyntheticKey method and add test * Make sure we dont react on @cds.odata.valuelist : false and add a test for that * Simplify filters in computeContextColumn * Use any single key as RPT-1 index column, not just fields called ID and add test * Adjust sample to changes in cds-feature-recomendations * Add comment about books entity in test model * Update javadoc * Do not register FioriRecommendationHandler if no PersistenceService is found * In RptInferenceClient:resolveIndexColumn: fall back to synthetic index column for non-string single keys * Add checks for the presence of keyNames * Minor changes * Remove reflection tests for resolveIndexColumn The index column resolution is also tested by nonIdKey_usesSyntheticKeyColumn and composedKeys_usesSyntheticKeyColumn in FioriRecommendationHandlerTest. * Extract RptIndexColumns utility to share index column logic between RptInferenceClient and MockRecommendationClient * Add synthetic Key (if needed) in the sdkRow creation * Refactor: remove keyNames from RecommendationClient interface — it is now an argument of the Resolver via RecommendationClientResolver<T> * chore: migrate to remote service (#80) * refactor(ai-core): migrate AICoreService to RemoteService - Remove AICoreService interface and AICoreServiceImpl - Introduce AICore constants class with SERVICE_NAME - Convert all handlers to use RemoteService with event contexts - Update DeploymentIdContext, InferenceClientContext, ResourceGroupContext - Update AICoreServiceConfiguration to register handlers on RemoteService - Adapt all unit tests to new RemoteService-based API - Update sample .cdsrc.json and ai-core-service.cds * chore(itests): adapt integration tests to RemoteService - Update all integration tests to use RemoteService + event contexts - Replace AICoreService.deploymentId/resourceGroup calls with context pattern - Update BaseIntegrationTest with new service resolution approach * refactor(recommendations): adapt to RemoteService API - Remove RptIndexColumns utility; inline resolveIndexColumn and addSyntheticKeyIfNeeded into RptInferenceClient - Change RecommendationClient.predict to accept keyNames as argument - Drop generic type parameter from RecommendationClientResolver; resolve now takes RemoteService directly - Remove keyNames from RptInferenceClient constructor (passed at predict time) - FioriRecommendationHandler now holds RemoteService reference and passes keyNames at prediction time - RecommendationConfiguration uses RemoteService + event context pattern (ResourceGroupContext, DeploymentIdContext, InferenceClientContext) - MockRecommendationClient simplified (no keyNames in constructor) - Update all tests to match new signatures * chore(samples): adapt bookshop sample to RemoteService - Replace AICoreService usage with RemoteService + event context pattern - Use AICore.SERVICE_NAME constant for service lookup - Demonstrate ResourceGroupContext, DeploymentIdContext, InferenceClientContext * update last Cqn references * update unit-tests * restore functionality * adapt tests * simplify itests * simplification * spotless * big blunder now fixed whoops * last occurences * spotless * feature: static recommendation state (#82) * refactor(ai-core): migrate AICoreService to RemoteService - Remove AICoreService interface and AICoreServiceImpl - Introduce AICore constants class with SERVICE_NAME - Convert all handlers to use RemoteService with event contexts - Update DeploymentIdContext, InferenceClientContext, ResourceGroupContext - Update AICoreServiceConfiguration to register handlers on RemoteService - Adapt all unit tests to new RemoteService-based API - Update sample .cdsrc.json and ai-core-service.cds * chore(itests): adapt integration tests to RemoteService - Update all integration tests to use RemoteService + event contexts - Replace AICoreService.deploymentId/resourceGroup calls with context pattern - Update BaseIntegrationTest with new service resolution approach * refactor(recommendations): adapt to RemoteService API - Remove RptIndexColumns utility; inline resolveIndexColumn and addSyntheticKeyIfNeeded into RptInferenceClient - Change RecommendationClient.predict to accept keyNames as argument - Drop generic type parameter from RecommendationClientResolver; resolve now takes RemoteService directly - Remove keyNames from RptInferenceClient constructor (passed at predict time) - FioriRecommendationHandler now holds RemoteService reference and passes keyNames at prediction time - RecommendationConfiguration uses RemoteService + event context pattern (ResourceGroupContext, DeploymentIdContext, InferenceClientContext) - MockRecommendationClient simplified (no keyNames in constructor) - Update all tests to match new signatures * chore(samples): adapt bookshop sample to RemoteService - Replace AICoreService usage with RemoteService + event context pattern - Use AICore.SERVICE_NAME constant for service lookup - Demonstrate ResourceGroupContext, DeploymentIdContext, InferenceClientContext * update last Cqn references * update unit-tests * restore functionality * adapt tests * simplify itests * simplification * spotless * big blunder now fixed whoops * last occurences * add filter * readme update * test adaption * cleanup * update readme --------- Signed-off-by: Marvin L <marvin.lindner@sap.com> * Final review doc changes (#84) * Document how to add the SAP_Recommendations navigation property manually * Add @odata.draft.enabled in README.md * Mark RPT-1 specific setting as such in README * Add Integer64 and UUID to the supported-types table * Drop Prerequisites section and include the info from there throughout the README * Changes to recommendations-test.cds * Remove 'detect automatically via ServiceBindingUtils' * fix recommendation unit tests (revert) * revert pr pipeline branches * continue-on-error * Fix one sonarqube issue apart from the coverage (#85) * simplification * adapt version --------- Signed-off-by: Marvin L <marvin.lindner@sap.com> Co-authored-by: Lisa Julia Nebel <lisa.nebel@sap.com>
* add missing info * adapt versions
…ates (#81) Bumps the minor-patch group with 6 updates in the / directory: | Package | From | To | | --- | --- | --- | | [com.sap.cloud.sdk:sdk-bom](https://github.com/SAP/cloud-sdk-java) | `5.30.0` | `5.31.0` | | [org.jacoco:jacoco-maven-plugin](https://github.com/jacoco/jacoco) | `0.8.14` | `0.8.15` | | [org.apache.maven.plugins:maven-surefire-plugin](https://github.com/apache/maven-surefire) | `3.5.5` | `3.5.6` | | [org.apache.maven.plugins:maven-failsafe-plugin](https://github.com/apache/maven-surefire) | `3.5.5` | `3.5.6` | | [com.github.spotbugs:spotbugs-maven-plugin](https://github.com/spotbugs/spotbugs-maven-plugin) | `4.9.8.3` | `4.10.2.0` | | [com.diffplug.spotless:spotless-maven-plugin](https://github.com/diffplug/spotless) | `3.5.1` | `3.6.0` | Updates `com.sap.cloud.sdk:sdk-bom` from 5.30.0 to 5.31.0 - [Release notes](https://github.com/SAP/cloud-sdk-java/releases) - [Changelog](https://github.com/SAP/cloud-sdk-java/blob/main/release_notes.md) - [Commits](SAP/cloud-sdk-java@rel/5.30.0...rel/5.31.0) Updates `org.jacoco:jacoco-maven-plugin` from 0.8.14 to 0.8.15 - [Release notes](https://github.com/jacoco/jacoco/releases) - [Commits](jacoco/jacoco@v0.8.14...v0.8.15) Updates `org.apache.maven.plugins:maven-surefire-plugin` from 3.5.5 to 3.5.6 - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](apache/maven-surefire@surefire-3.5.5...surefire-3.5.6) Updates `org.apache.maven.plugins:maven-failsafe-plugin` from 3.5.5 to 3.5.6 - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](apache/maven-surefire@surefire-3.5.5...surefire-3.5.6) Updates `com.github.spotbugs:spotbugs-maven-plugin` from 4.9.8.3 to 4.10.2.0 - [Release notes](https://github.com/spotbugs/spotbugs-maven-plugin/releases) - [Commits](spotbugs/spotbugs-maven-plugin@spotbugs-maven-plugin-4.9.8.3...spotbugs-maven-plugin-4.10.2.0) Updates `com.diffplug.spotless:spotless-maven-plugin` from 3.5.1 to 3.6.0 - [Release notes](https://github.com/diffplug/spotless/releases) - [Changelog](https://github.com/diffplug/spotless/blob/main/CHANGES.md) - [Commits](diffplug/spotless@maven/3.5.1...maven/3.6.0) --- updated-dependencies: - dependency-name: com.diffplug.spotless:spotless-maven-plugin dependency-version: 3.6.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: com.github.spotbugs:spotbugs-maven-plugin dependency-version: 4.10.2.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: com.sap.cloud.sdk:sdk-bom dependency-version: 5.31.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: org.apache.maven.plugins:maven-failsafe-plugin dependency-version: 3.5.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: org.apache.maven.plugins:maven-surefire-plugin dependency-version: 3.5.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: org.jacoco:jacoco-maven-plugin dependency-version: 0.8.15 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps the minor-patch group with 1 update in the / directory: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 6.0.2 to 6.0.3 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](actions/checkout@de0fac2...df4cb1c) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Marvin L <marvin.lindner@sap.com>
* Initial commit
* feat: initialize CAP bookshop sample application
* add plugin scaffold
* update plugin scaffold
* enable attachments
remove extra files
rename handler
tiny fixes
* enable attachments
remove extra files
rename handler
tiny fixes
* setup orchestrator
* rename handler and update to junit 5
* initial Extraction Orchestrator setup
* async process :)
* add tests
* bounded thread pool
* review fixes
* revert unintended change
* review fixes
* review fixes
* make the working directory path absolute
* enable attachments
remove extra files
rename handler
tiny fixes
setup orchestrator
initial Extraction Orchestrator setup
async process :)
add tests
bounded thread pool
# Conflicts:
# sap-document-ai/pom.xml
# sap-document-ai/src/main/java/com/sap/cds/handlers/AttachmentEventHandler.java
# sap-document-ai/src/main/resources/META-INF/services/com.sap.cds.services.runtime.CdsRuntimeConfiguration
# sap-document-ai/src/test/java/com/sap/cds/AttachmentEventHandlerTest.java
* attachment content retrieval
clean up
* clean up
* tiny improvements / comments
* chore(hyperspace): 🤖 Add PR Bot Configuration
* Create main.yml
move the file one level lower
attempt to fix failing test
test
clean up
configure pmd, jacoco and mvn spotless
fix version
tests
* initial commit
* credential handling using service binding and running using local hybrid mode
* update pom.xml
* tests
* bot review fixes
* apply spotless
* skip extraction early when Document AI service is unavailable
* custom flows :)
* remove sonar code
* remove threads
* initial commit
bot review fixes
Remove MediaData from the core
# Conflicts:
# sap-document-ai/src/main/java/com/sap/cds/service/DefaultDocumentAiProcessingService.java
# sap-document-ai/src/main/java/com/sap/cds/service/ExtractionServiceImpl.java
# sap-document-ai/src/main/java/com/sap/cds/service/StatusTransitionValidator.java
# sap-document-ai/src/main/resources/cds/com.sap.cds/sap-document-ai/extraction-job.cds
# sap-document-ai/src/test/java/com/sap/cds/service/DefaultDocumentAiProcessingServiceTest.java
# sap-document-ai/src/test/java/com/sap/cds/service/ExtractionServiceImplTest.java
# sap-document-ai/src/test/java/com/sap/cds/service/StatusTransitionValidatorTest.java
* fix: review fixes
* refactor: use outbox
* feat: add standalone document upload entry point via Jobs media entity
feat: make cds-feature-attachments dependency optional
fix: review fixes
refactor: rebase onto the parent branch
test: add/update tests
refactor: the workflow
refactor: persistence to typed service
fix: test file
refactor: optimise exception files
fix: review fixes by bot
* refactor: remove attachments plugin entry point
* refactor: rename registration files / classes
* feat: implement programmatic way of using the plugin
* fix: review fixes
* feat: outbox + scheduler + results
refactor: refactor names of statuses to align with DIE service
refactor: update cds version
refactor: update cds version
Delete sap-document-ai/sap-document-ai.iml
feat: implement outbox + task scheduler + result extraction
refactor: emit DocumentExtractionResult anonymously
Revert "refactor: emit DocumentExtractionResult anonymously"
This reverts commit 8802f3d6cb9d5a7fbff5a9afd15816690a34f731.
refactor: make buildoptions dynamic
refactor: revert variable renaming
refactor: apply sonarcube changes
test: add more unit tests
refactor: cleanup
* fix: review fix
* fix: fix: use wildcard ServiceName for DocumentExtractionResult handler to decouple consumer from plugin service name
* chore: update the cds version to LTS
* docs: add javadcos to classes and methods
* fix: review fix
* docs: write documentation about the plugin
* feat: make polling intervals dynamic
* refactor: rename all packages to com.sap.cds.feature.documentai.* + enable debug logs
* docs: remove integration tests from documentation
* docs: remove integration tests from documentation
* chore: apply spotless
* tests: add integration tests
refactor: rename integration test files
refactor: turn off logs
* fix: review fixes
* refactor(document-ai): reorganize imported tree into cds-ai layout
- Hoist the plugin Maven module up: sap-document-ai/{pom.xml,src} -> cds-feature-sap-document-ai/{pom.xml,src}
- Move imported bookshop sample to samples/document-ai-bookshop/
- Move imported integration test Java sources into integration-tests/spring/
under com.sap.cds.feature.documentai.integrationtest (Application, config
and remaining files are handled in later commits).
Structural-only change; no pom or Java content is modified in this commit.
* chore(document-ai): remove duplicated and obsolete imported files
- Delete the imported LICENSE (Apache 2.0 already present at repo root, only
whitespace-different).
- Delete the imported .github/ workflows and .gitignore; both are superseded
by the repo-level equivalents in cds-ai.
- Delete .hyperspace/pull_request_bot.json (byte-identical to the copy at
repo root).
- Delete the leftover cds-feature-sap-document-ai/integration-tests/
directory: its Java test sources were moved into integration-tests/spring
in the previous commit; the remaining Application.java, application.yaml,
logback-test.xml, pom.xml, package.json will be reintegrated into the
spring integration-tests module in commit 5.
* refactor(document-ai): adapt plugin pom to cds-ai conventions
- Set parent to cds-ai-root, drop local version, groupId, dependencyManagement
and spotless/pmd/spotbugs/compiler configuration (inherit from root).
- Depend on cds-services-api, cds-services-utils, cds4j-core, cds-services-impl
and connectivity-apache-httpclient5 (versions managed by root parent or
the SAP Cloud SDK BOM).
- Add hermetic CDS build: cds.install-node and cds.npm-ci executions plus a
local package.json pinning @sap/cds-dk 9.9.1 (matches cds-feature-ai-core).
- Keep the plugin's existing cds.build layout (src/main/resources/cds/
com.sap.cds/sap-document-ai) via a workingDirectory-based invocation of
the cds-maven-plugin cds goal, mirroring cds-feature-ai-core.
- Add cds.generate for basePackage com.sap.cds.feature.documentai.generated.cds4j.
- Add a module spotbugs-exclusion-filter.xml so the spotbugs check inherited
from the root pluginManagement can resolve its excludeFilterFile.
- Wire module-level jacoco (prepare-agent, report) matching sibling modules.
* refactor(document-ai): normalize license headers and plugin name references
- Rewrite the copyright header in every imported Java file (plugin sources,
moved bookshop handlers and moved integration tests):
* update contributor line from "cds-feature-sap-document-ai contributors"
to "cds-ai contributors" so files match the license header enforced by
the root spotless configuration;
* fix indentation from "* " / "*/" to " * " / " */" to match the
canonical style used by the rest of the repository.
- Update the DocumentExtractionHandler error message in the bookshop sample
to reference the new artifactId cds-feature-sap-document-ai.
* chore(document-ai): wire plugin, integration tests and sample into reactor
Root pom:
- Register cds-feature-sap-document-ai as a Maven module.
- Add a dependencyManagement entry for cds-feature-sap-document-ai using
${revision} so downstream modules pick up the CI-friendly version.
Integration tests (integration-tests/spring):
- Add cds-feature-sap-document-ai (managed) as a test-scope reachable
dependency so tests can reach the plugin's generated cds4j classes and
runtime handlers.
- Add org.awaitility:awaitility 4.2.2 (test) used by the moved tests.
- Add "using from 'com.sap.cds/sap-document-ai';" to test-service.cds so
the CDS build for the integration-tests reactor includes the plugin
model and the H2 deploy provisions the ExtractionJob table.
- Rename moved *ITest.java files to *Test.java so surefire picks them up
(the spring integration-tests module runs everything through surefire
and does not have failsafe wired), and update internal class references.
Samples (samples/document-ai-bookshop):
- Swap plugin coordinates from sap-document-ai:1.0-SNAPSHOT to
com.sap.cds:cds-feature-sap-document-ai using a version property.
- Pin cds-feature-sap-document-ai.version to 0.0.1-alpha (matches root
${revision}) and align cds.services.version to the repo default 4.9.0.
* fix(document-ai): resolve missing dependency version and CI-friendly build blockers
- Set explicit version on cds4j-core dependency (com.sap.cds:cds4j-core is
not managed by cds-services-bom); use ${cds.services.version} to keep it
aligned with the rest of the CDS stack.
- Commit package-lock.json so the cds-maven-plugin cds.npm-ci execution can
run non-interactively (mirrors cds-feature-ai-core and cds-feature-
recommendations).
- Extend the module-local spotbugs-exclusion-filter.xml to also skip the
DM_DEFAULT_ENCODING pattern for classes matching *Test / *TestBase. The
imported unit tests use byte fixtures that intentionally rely on the
platform default encoding; production code is not affected.
* style: apply spotless across the reactor
Ran `mvn spotless:apply` at the repo root. Reformatting affects:
- The moved doc-ai integration tests under integration-tests/spring
(five files): minor line-break normalization introduced during the
cds-feature-sap-document-ai import.
- One pre-existing formatting nit in
cds-feature-ai-core/.../MockAICoreApiHandler.java that the previous
runs of spotless never caught because the spotless check phase is
gated by `${spotless.check.skip}` (default true) in the root pom.
No functional changes; unit and integration tests still pass.
* ci: preinstall cds-feature-sap-document-ai for integration tests
The integration-tests/spring module now compile-depends on
com.sap.cds:cds-feature-sap-document-ai:${revision}. The composite
action for the Integration Tests job first installs a small subset of
plugins to ~/.m2 with a -pl list and then runs `mvn clean verify`
against integration-tests/pom.xml. The new plugin was missing from
that list, so dependency resolution failed with:
Could not find artifact com.sap.cds:cds-feature-sap-document-ai:
jar:0.0.1-alpha in central
Add cds-feature-sap-document-ai to the -pl list so the plugin gets
built and installed locally before the integration-tests reactor
runs. -am still resolves the transitive dependency graph correctly.
* fix(mtx-local): pin @sap/cds ^9 at workspace root to prevent duplicate cds install
Reproducing the failing CI 'Local MTX Tests' job locally showed that a
fresh 'npm install' in integration-tests/mtx-local/ ends up with three
copies of @sap/cds:
- mtx-local/node_modules/@sap/cds @ 10.0.3 (hoisted)
- mtx-local/node_modules/@sap/cds-dk/node_modules/@sap/cds @ 9.9.1
- mtx-local/mtx/sidecar/node_modules/@sap/cds @ 9.9.2
The sidecar boots with 'cds-serve --profile development' and refuses to
start with:
ERROR: @sap/cds was loaded from different locations
Root cause:
* @sap/cds 10.0.3 was published to npm (major bump from 9.x).
* @sap/cds-mtxs 3.9.5 (latest 3.x) declares peer '@sap/cds: >=9' -
the loose lower bound. With no top-level pin, npm satisfies that
peer with the newest matching version and hoists 10.0.3.
* The sidecar itself declares '@sap/cds: ^9' (strict 9.x) so it
keeps a nested 9.x copy. Same for @sap/cds-dk, whose own dep
range '^8.3 || ^9' can not accept 10.x.
Result: three copies of @sap/cds, cds-serve aborts.
Fix: declare '@sap/cds: ^9' at the workspace root as a devDependency.
This gives npm a top-level constraint that pins the hoisted @sap/cds
to 9.x, which simultaneously satisfies cds-mtxs's '>=9', cds-dk's
'^8.3 || ^9' and the sidecar's '^9' - all resolve to the same hoisted
9.x copy. Verified locally with a full 'mvn clean verify -pl
integration-tests/mtx-local/srv -am -P mtx-integration-tests' run:
8 MTX tests pass, sidecar starts cleanly.
The 'CI - PR' job for main was not affected earlier only because @sap/
cds 10 was published between PR 95's run (2026-07-02) and PR 96's run
(2026-07-03).
* adapt readme
* chore(document-ai): note deferred inclusion in cds-starter-ai
Document the intentional omission of cds-feature-sap-document-ai from
the cds-starter-ai aggregator. The plugin is not fully multi-tenant
ready yet, so bundling it into the starter would surprise multi-tenant
CAP applications.
- Add a TODO comment inside cds-starter-ai/pom.xml so contributors
see the intent at the point that would need to change.
- Add a short note under the Starter section of the root README so
consumers know to add cds-feature-sap-document-ai as an explicit
dependency if they need it today.
* refactor(samples): consolidate document-ai-bookshop into samples/bookshop
The two sample apps at samples/bookshop and samples/document-ai-bookshop
both called "bookshop" and had overlapping filenames (admin-service.cds,
cat-service.cds, package.json...). Consolidate them into a single CAP
Java sample that demonstrates all AI plugins in the repository side by
side, matching CAP idioms (one app, multiple services).
Changes to samples/bookshop:
- Add cds-feature-sap-document-ai and cds-feature-attachments to the
dependencyManagement of the parent pom and to the srv module deps.
- Add "com.sap.cds/sap-document-ai" and "com.sap.cds/cds-feature-
attachments" under "cds.requires" in package.json so the Maven-based
CDS models are picked up during the cds build.
- Extend .cdsrc.json cdsc.moduleLookupDirectories with "target/cds/"
so cds finds the models that cds-maven-plugin's resolve goal extracts
there.
- Add srv/attachments.cds extending Books with an Attachments composition,
reused from the previous document-ai-bookshop.
- Extend AdminService.Books with a bound extractDocumentData() action.
- Add DocumentExtractionHandler and DocumentExtractionResultHandler under
customer.bookshop.handlers.
- Merge doc-ai-specific configuration into srv/src/main/resources/
application.yaml (persistent outbox scheduler, polling interval, logging).
Delete samples/document-ai-bookshop/ entirely.
Update the plugin READMEs (cds-feature-ai-core, cds-feature-recommendations,
cds-feature-sap-document-ai) to point at samples/bookshop as the runnable
sample.
* chore(coverage): include cds-feature-sap-document-ai in aggregated report
Extend the coverage-report aggregator so the new plugin appears in the
merged JaCoCo report and its coverage feeds into SonarQube:
- Add cds-feature-sap-document-ai as a compile-scope dependency.
- Add the module's target/classes to the copy-classes resource execution
(jacoco:check requires the classes on the aggregator's classpath).
- Add cds-feature-sap-document-ai/target/jacoco.exec to the jacoco:merge
fileset that produces the aggregated exec data file.
The coverage check executions (check-ai-core, check-recommendations)
are kept unchanged here; they are removed in a follow-up commit.
* chore(coverage): remove no-op JaCoCo check executions
The check-ai-core and check-recommendations executions used
<minimum>0.00</minimum> for all counters (INSTRUCTION, BRANCH,
COMPLEXITY), meaning they always passed and provided no build-time
gate. Coverage is enforced by SonarQube in the pipeline, so the
duplicated JaCoCo checks are dead configuration.
Remove both executions. Keep jacoco-merge and jacoco-aggregate-report
which still feed the aggregated coverage data into SonarQube.
* docs: update root README for cds-feature-sap-document-ai
- Add cds-feature-sap-document-ai row to the Plugins table.
- Shorten the Samples section to a pointer at samples/bookshop, which
now unifies the sample apps that demonstrate all plugins together.
- Shorten the Local Development section: keep the top-level build and
test commands, drop the module-specific AI Core / integration-tests
invocation and point to the individual module READMEs and to the
integration-tests/spring module instead.
* refactor(samples): give recommendations and document-ai each their own service
* refactor(samples): simplify use case
* simplify and spotless
* latest cahgnes from upstream
---------
Co-authored-by: Samyuktha Prabhu <mangalore.samyuktha.prabhu@sap.com>
Co-authored-by: samyuktaprabhu <33195453+samyuktaprabhu@users.noreply.github.com>
Co-authored-by: hyperspace-insights[bot] <209611008+hyperspace-insights[bot]@users.noreply.github.com>
Co-authored-by: Robin <r.de.silva.jayasinghe@sap.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the full prototype implementation for review purposes.
Note: This PR is for code review only — do not merge it. Close it once the review is complete.