From defe84079a9dca6dca928a6e4d20b944ed16fb59 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 9 Aug 2026 20:51:28 +0900 Subject: [PATCH 01/15] test: stop publishing Qlik as a row-write target --- .../WarehouseBiConnectorSurfaceTest.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/etl-service/src/test/java/com/xtrmetl/etl/connector/WarehouseBiConnectorSurfaceTest.java b/etl-service/src/test/java/com/xtrmetl/etl/connector/WarehouseBiConnectorSurfaceTest.java index cd9c7349..acca0a94 100644 --- a/etl-service/src/test/java/com/xtrmetl/etl/connector/WarehouseBiConnectorSurfaceTest.java +++ b/etl-service/src/test/java/com/xtrmetl/etl/connector/WarehouseBiConnectorSurfaceTest.java @@ -15,15 +15,14 @@ import static org.junit.jupiter.api.Assertions.assertTrue; /** - * Unit tests on shipped warehouse/BI connector classes (not live SaaS). + * Unit tests on warehouse target connectors intentionally exposed by the production registry. */ class WarehouseBiConnectorSurfaceTest { static Stream scaffolds() { return Stream.of( new DatabricksTargetConnector(), - new SnowflakeTargetConnector(), - new QlikSenseTargetConnector() + new SnowflakeTargetConnector() ); } @@ -83,11 +82,11 @@ void snowflakeRequiresAccountPathKeys() { } @Test - void qlikRequiresTenantAuthApp() { - QlikSenseTargetConnector c = new QlikSenseTargetConnector(); - assertEquals("qlik-sense", c.id()); - assertTrue(c.requiredConfigKeys().containsAll( - List.of("tenant-url", "api-key", "app-id"))); + void qlikIsNotPublishedAsARowWriteTargetUntilItsRealProductBoundaryExists() { + TargetConnectorRegistry registry = new TargetConnectorRegistry(); + + assertTrue(registry.find("qlik-sense").isEmpty()); + assertTrue(registry.all().stream().noneMatch(connector -> connector.id().equals("qlik-sense"))); } @Test @@ -133,12 +132,13 @@ void dispatcherValidatesBoundConfigBeforeScaffoldRefusal() { } @Test - void catalogExposesRequiredKeysAndIntegrationHooks() { + void catalogExposesOnlyIntentionalWarehouseScaffolds() { TargetConnectorDispatcher dispatcher = new TargetConnectorDispatcher(new TargetConnectorRegistry(), new ConnectorProperties()); List> catalog = dispatcher.catalog(); - assertEquals(3, catalog.size()); + assertEquals(2, catalog.size()); + assertTrue(catalog.stream().noneMatch(row -> "qlik-sense".equals(row.get("id")))); for (Map row : catalog) { assertEquals("SCAFFOLD", row.get("status")); assertEquals(false, row.get("writable")); From d8cf44a2dfe61dc0997ba12b6bccb593b22b7a63 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 9 Aug 2026 21:06:25 +0900 Subject: [PATCH 02/15] fix: stop publishing Qlik as row-write target --- .../java/com/xtrmetl/etl/connector/TargetConnectorRegistry.java | 1 - 1 file changed, 1 deletion(-) diff --git a/etl-service/src/main/java/com/xtrmetl/etl/connector/TargetConnectorRegistry.java b/etl-service/src/main/java/com/xtrmetl/etl/connector/TargetConnectorRegistry.java index 4e5390af..636dbdf7 100644 --- a/etl-service/src/main/java/com/xtrmetl/etl/connector/TargetConnectorRegistry.java +++ b/etl-service/src/main/java/com/xtrmetl/etl/connector/TargetConnectorRegistry.java @@ -19,7 +19,6 @@ public class TargetConnectorRegistry { public TargetConnectorRegistry() { register(new DatabricksTargetConnector()); register(new SnowflakeTargetConnector()); - register(new QlikSenseTargetConnector()); } public final void register(TargetConnector connector) { From 4c1c0d2b09d16aef6cc428455a14a0e00f117b30 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 9 Aug 2026 21:06:56 +0900 Subject: [PATCH 03/15] docs: mark Qlik row-write surface unregistered --- docs/connectors/qlik-sense.md | 49 +++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/docs/connectors/qlik-sense.md b/docs/connectors/qlik-sense.md index 119dc050..29869bd0 100644 --- a/docs/connectors/qlik-sense.md +++ b/docs/connectors/qlik-sense.md @@ -1,37 +1,42 @@ -# Qlik Sense connector (scaffold) +# Qlik Sense integration (planned) ## Intent -Push or expose change data for Qlik Sense (SaaS or client-managed) so apps can reload against an up-to-date source. +Integrate mightyETL with Qlik Sense / Qlik Cloud only through a product boundary that matches Qlik's actual APIs and operating model. Until that boundary is implemented and verified, Qlik is not published as a mightyETL target connector. ## Status | Item | State | |:-----|:------| -| Product claim | **Not supported in runtime** (`ConnectorStatus.SCAFFOLD`) | -| SPI class | `QlikSenseTargetConnector` | -| Config binding (`xtrmetl.connectors.qlik-sense.*`) | Yes | -| Required-key validation | Yes (tenant-url, api-key, app-id) | -| Catalog API fields | Yes (`GET /api/etl/connectors`) | -| Qlik REST / qrs API client | **No** | +| Product claim | **Not supported in runtime** | +| Production target registry | **Not registered** | +| Catalog API (`GET /api/etl/connectors`) | **Not advertised** | +| Reference SPI class | `QlikSenseTargetConnector` remains as non-registered design/reference code | +| Config binding (`xtrmetl.connectors.qlik-sense.*`) | Historical/reference surface only; not a production capability claim | +| Qlik reload API client | **No** | +| Qlik data-file upload/import client | **No** | | Direct QVD writer | **No** | -| Live write path | **Refused** (`UnsupportedOperationException`) | +| Live row-write path | **No** | -## Planned integration shapes (choose later) +## Product boundary -1. **Indirect (recommended first):** mightyETL lands data in Postgres/Snowflake/Databricks; Qlik connects to that warehouse. Connector only triggers **reload** via Qlik API. -2. **Direct:** REST bulk load into a Qlik data connection (product-specific; higher complexity). +The previous scaffold modeled Qlik as a `TargetConnector.write(...)` row sink even though it never implemented a Qlik client and always refused writes. That shape is no longer advertised through the production registry. -## Config keys +Current Qlik Cloud APIs expose product-specific operations such as application reload orchestration and data-file upload/import. A future mightyETL integration should therefore be designed explicitly around one of those supported boundaries instead of pretending that Qlik is a transactional warehouse row sink. -| Key | Required | Description | -|:----|:--------:|:------------| -| `tenant-url` | yes | Qlik Cloud tenant or Sense enterprise base URL | -| `api-key` | yes | Auth (**secret**) | -| `app-id` | yes | App to reload | -| `mode` | no | `reload-only` \| `push` (push not designed yet) | +## Planned integration shapes -## Limitations (honest) +1. **Warehouse + reload orchestration:** mightyETL lands data in a supported warehouse or database, then a separately designed Qlik integration triggers and observes an application reload. +2. **Data-file workflow:** mightyETL produces a governed file artifact and uses Qlik's supported data-file APIs before a reload, with explicit idempotency, authentication, tenancy, retry, audit, and cleanup contracts. -- No Qlik SDK dependency, no reload automation, no multi-tenant isolation. -- Prefer documenting Qlik → Postgres/Kafka as the supported path until a live client ships. +Neither path is implemented by the current `TargetConnector` SPI. A future implementation requires a separate PRD/TRD/API contract and realistic integration tests before production registration. + +## Historical configuration surface + +The reference class still documents the historical keys `tenant-url`, `api-key`, `app-id`, and optional `mode`. Those keys do not grant production support while the connector is absent from `TargetConnectorRegistry`. + +## Limitations + +- No Qlik API client or SDK integration is shipped. +- No reload automation, file upload/import workflow, QVD writer, or tenant-aware Qlik execution exists. +- Qlik must remain absent from production connector discovery until an actual supported integration path has been implemented and verified. From ceecd8c3bbb7d9b861d31f2d78b30eee3d14dc45 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 9 Aug 2026 21:16:31 +0900 Subject: [PATCH 04/15] test(connectors): align registry assertions with Qlik removal --- .../xtrmetl/etl/connector/TargetConnectorRegistryTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/etl-service/src/test/java/com/xtrmetl/etl/connector/TargetConnectorRegistryTest.java b/etl-service/src/test/java/com/xtrmetl/etl/connector/TargetConnectorRegistryTest.java index 2c11bb1e..73681f6b 100644 --- a/etl-service/src/test/java/com/xtrmetl/etl/connector/TargetConnectorRegistryTest.java +++ b/etl-service/src/test/java/com/xtrmetl/etl/connector/TargetConnectorRegistryTest.java @@ -13,13 +13,13 @@ class TargetConnectorRegistryTest { @Test - void registersScaffoldConnectorsForWarehouseAndBiTargets() { + void registersOnlyAdvertisedWarehouseScaffolds() { TargetConnectorRegistry registry = new TargetConnectorRegistry(); - assertEquals(3, registry.all().size()); + assertEquals(2, registry.all().size()); assertTrue(registry.find("databricks").isPresent()); assertTrue(registry.find("snowflake").isPresent()); - assertTrue(registry.find("qlik-sense").isPresent()); + assertFalse(registry.find("qlik-sense").isPresent()); for (TargetConnector connector : registry.all()) { assertEquals(ConnectorStatus.SCAFFOLD, connector.status()); From 000c9e74deb321a53dcf2f4521847cd264fcde44 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 9 Aug 2026 21:17:43 +0900 Subject: [PATCH 05/15] test(connectors): align catalog size with Qlik removal --- .../xtrmetl/etl/connector/TargetConnectorDispatcherTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/etl-service/src/test/java/com/xtrmetl/etl/connector/TargetConnectorDispatcherTest.java b/etl-service/src/test/java/com/xtrmetl/etl/connector/TargetConnectorDispatcherTest.java index 3a9dafc1..b3a0d906 100644 --- a/etl-service/src/test/java/com/xtrmetl/etl/connector/TargetConnectorDispatcherTest.java +++ b/etl-service/src/test/java/com/xtrmetl/etl/connector/TargetConnectorDispatcherTest.java @@ -58,11 +58,12 @@ void failsValidationWhenScaffoldEnabledButConfigIncomplete() { } @Test - void catalogShowsAllConnectorsDisabledByDefault() { + void catalogShowsAdvertisedConnectorsDisabledByDefault() { TargetConnectorDispatcher dispatcher = new TargetConnectorDispatcher(new TargetConnectorRegistry(), new ConnectorProperties()); - assertEquals(3, dispatcher.catalog().size()); + assertEquals(2, dispatcher.catalog().size()); + assertTrue(dispatcher.catalog().stream().noneMatch(row -> "qlik-sense".equals(row.get("id")))); assertTrue(dispatcher.catalog().stream().noneMatch(row -> Boolean.TRUE.equals(row.get("writable")))); assertTrue(dispatcher.catalog().stream().noneMatch(row -> Boolean.TRUE.equals(row.get("opened")))); } From 0320b6be9d006d749f44438758b6d182cd7cba95 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 9 Aug 2026 21:18:42 +0900 Subject: [PATCH 06/15] test(api): align connector catalog with Qlik removal --- .../java/com/xtrmetl/etl/controller/EtlControllerTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/etl-service/src/test/java/com/xtrmetl/etl/controller/EtlControllerTest.java b/etl-service/src/test/java/com/xtrmetl/etl/controller/EtlControllerTest.java index 9b935263..6c71e9b7 100644 --- a/etl-service/src/test/java/com/xtrmetl/etl/controller/EtlControllerTest.java +++ b/etl-service/src/test/java/com/xtrmetl/etl/controller/EtlControllerTest.java @@ -243,13 +243,15 @@ void mapsUnexpectedFailureToGenericInternalProblem() throws Exception { } @Test - void connectorCatalogRemainsAvailable() throws Exception { + void connectorCatalogRemainsAvailableWithoutQlikRowWriteScaffold() throws Exception { mockMvc.perform(get("/api/etl/connectors")) .andExpect(status().isOk()) .andExpect(jsonPath("$.product").value("mightyETL")) .andExpect(jsonPath("$.primaryLoadPath").value("postgresql")) - .andExpect(jsonPath("$.connectors.length()").value(3)) + .andExpect(jsonPath("$.connectors.length()").value(2)) .andExpect(jsonPath("$.connectors[?(@.id == 'databricks')]").exists()) + .andExpect(jsonPath("$.connectors[?(@.id == 'snowflake')]").exists()) + .andExpect(jsonPath("$.connectors[?(@.id == 'qlik-sense')]").doesNotExist()) .andExpect(jsonPath("$.connectors[*].requiredConfigKeys").exists()) .andExpect(jsonPath("$.connectors[*].integration").exists()); } From 951e94310311a7c8eb0d5ae145f008ecfdc9f0c5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 9 Aug 2026 21:53:04 +0900 Subject: [PATCH 07/15] test(connectors): retire Qlik production config surface --- ...kConnectorConfigurationRetirementTest.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 etl-service/src/test/java/com/xtrmetl/etl/config/QlikConnectorConfigurationRetirementTest.java diff --git a/etl-service/src/test/java/com/xtrmetl/etl/config/QlikConnectorConfigurationRetirementTest.java b/etl-service/src/test/java/com/xtrmetl/etl/config/QlikConnectorConfigurationRetirementTest.java new file mode 100644 index 00000000..7e9417bf --- /dev/null +++ b/etl-service/src/test/java/com/xtrmetl/etl/config/QlikConnectorConfigurationRetirementTest.java @@ -0,0 +1,38 @@ +package com.xtrmetl.etl.config; + +import com.xtrmetl.etl.connector.ConnectorProperties; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Guards removal of Qlik row-write configuration from the production connector surface while the + * real Qlik integration remains intentionally unimplemented. + */ +class QlikConnectorConfigurationRetirementTest { + + @Test + void productionAliasRegistryDoesNotAdvertiseQlikRowWriteEnablement() { + assertFalse( + MightyEtlConfigAliasEnvironmentPostProcessor.RELATIVE_KEYS + .contains("connectors.qlik-sense.enabled") + ); + } + + @Test + void productionConnectorPropertiesDoNotBindQlikRowWriteCredentials() { + assertThrows(NoSuchMethodException.class, () -> ConnectorProperties.class.getMethod("getQlikSense")); + assertFalse( + Arrays.stream(ConnectorProperties.class.getDeclaredClasses()) + .anyMatch(type -> type.getSimpleName().equals("QlikSenseProps")) + ); + + ConnectorProperties properties = new ConnectorProperties(); + assertFalse(properties.isEnabled("qlik-sense")); + assertTrue(properties.configMap("qlik-sense").isEmpty()); + } +} From a5cd022032f657a809d9a2a8a66a715cbd44886a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 9 Aug 2026 21:53:53 +0900 Subject: [PATCH 08/15] fix(connectors): remove Qlik production property binding --- .../etl/connector/ConnectorProperties.java | 60 +------------------ 1 file changed, 3 insertions(+), 57 deletions(-) diff --git a/etl-service/src/main/java/com/xtrmetl/etl/connector/ConnectorProperties.java b/etl-service/src/main/java/com/xtrmetl/etl/connector/ConnectorProperties.java index ae07ae4e..41ddd2da 100644 --- a/etl-service/src/main/java/com/xtrmetl/etl/connector/ConnectorProperties.java +++ b/etl-service/src/main/java/com/xtrmetl/etl/connector/ConnectorProperties.java @@ -6,8 +6,9 @@ import java.util.Map; /** - * Target connector enable flags and binding surface for warehouse/BI scaffolds. - * Enabling a scaffold without a real implementation is refused by {@link TargetConnectorDispatcher}. + * Target connector enable flags and binding surface for warehouse scaffolds that remain in the + * production registry. Enabling a scaffold without a real implementation is refused by + * {@link TargetConnectorDispatcher}. * *

Prefix: {@code xtrmetl.connectors.*} (dual-read from {@code mightyetl.connectors.*}).

*/ @@ -16,7 +17,6 @@ public class ConnectorProperties { private final DatabricksProps databricks = new DatabricksProps(); private final SnowflakeProps snowflake = new SnowflakeProps(); - private final QlikSenseProps qlikSense = new QlikSenseProps(); public DatabricksProps getDatabricks() { return databricks; @@ -26,15 +26,10 @@ public SnowflakeProps getSnowflake() { return snowflake; } - public QlikSenseProps getQlikSense() { - return qlikSense; - } - public boolean isEnabled(String connectorId) { return switch (connectorId) { case "databricks" -> databricks.isEnabled(); case "snowflake" -> snowflake.isEnabled(); - case "qlik-sense" -> qlikSense.isEnabled(); default -> false; }; } @@ -47,7 +42,6 @@ public Map configMap(String connectorId) { return switch (connectorId) { case "databricks" -> databricks.toConfigMap(); case "snowflake" -> snowflake.toConfigMap(); - case "qlik-sense" -> qlikSense.toConfigMap(); default -> Map.of(); }; } @@ -250,54 +244,6 @@ Map toConfigMap() { } } - public static class QlikSenseProps extends ConnectorFlag { - private String tenantUrl = ""; - private String apiKey = ""; - private String appId = ""; - private String mode = "reload-only"; - - public String getTenantUrl() { - return tenantUrl; - } - - public void setTenantUrl(String tenantUrl) { - this.tenantUrl = tenantUrl; - } - - public String getApiKey() { - return apiKey; - } - - public void setApiKey(String apiKey) { - this.apiKey = apiKey; - } - - public String getAppId() { - return appId; - } - - public void setAppId(String appId) { - this.appId = appId; - } - - public String getMode() { - return mode; - } - - public void setMode(String mode) { - this.mode = mode; - } - - Map toConfigMap() { - Map map = new LinkedHashMap<>(); - putIfPresent(map, "tenant-url", tenantUrl); - putIfPresent(map, "api-key", apiKey); - putIfPresent(map, "app-id", appId); - putIfPresent(map, "mode", mode); - return map; - } - } - private static void putIfPresent(Map map, String key, String value) { if (value != null && !value.isBlank()) { map.put(key, value); From 1a334ca5d0d85977cb3b5dfba30cd93308f3a10b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 9 Aug 2026 21:54:21 +0900 Subject: [PATCH 09/15] fix(connectors): remove Qlik config alias --- .../config/MightyEtlConfigAliasEnvironmentPostProcessor.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/etl-service/src/main/java/com/xtrmetl/etl/config/MightyEtlConfigAliasEnvironmentPostProcessor.java b/etl-service/src/main/java/com/xtrmetl/etl/config/MightyEtlConfigAliasEnvironmentPostProcessor.java index 5462db4b..ea2f8c05 100644 --- a/etl-service/src/main/java/com/xtrmetl/etl/config/MightyEtlConfigAliasEnvironmentPostProcessor.java +++ b/etl-service/src/main/java/com/xtrmetl/etl/config/MightyEtlConfigAliasEnvironmentPostProcessor.java @@ -29,8 +29,7 @@ public class MightyEtlConfigAliasEnvironmentPostProcessor implements Environment "etl.max-batch-records", "etl.jobs.intake-enabled", "connectors.databricks.enabled", - "connectors.snowflake.enabled", - "connectors.qlik-sense.enabled" + "connectors.snowflake.enabled" ); /** From 37d3880c6540440df924c40d86d50d8304f0906a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 9 Aug 2026 21:54:55 +0900 Subject: [PATCH 10/15] docs(connectors): retire Qlik production config claim --- docs/connectors/qlik-sense.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/connectors/qlik-sense.md b/docs/connectors/qlik-sense.md index 29869bd0..48795274 100644 --- a/docs/connectors/qlik-sense.md +++ b/docs/connectors/qlik-sense.md @@ -2,7 +2,7 @@ ## Intent -Integrate mightyETL with Qlik Sense / Qlik Cloud only through a product boundary that matches Qlik's actual APIs and operating model. Until that boundary is implemented and verified, Qlik is not published as a mightyETL target connector. +Integrate mightyETL with Qlik Sense / Qlik Cloud only through a product boundary that matches Qlik's actual APIs and operating model. Until that boundary is implemented and verified, Qlik is not published as a mightyETL target connector or production connector configuration surface. ## Status @@ -11,8 +11,9 @@ Integrate mightyETL with Qlik Sense / Qlik Cloud only through a product boundary | Product claim | **Not supported in runtime** | | Production target registry | **Not registered** | | Catalog API (`GET /api/etl/connectors`) | **Not advertised** | +| Production config binding (`xtrmetl.connectors.qlik-sense.*`) | **Not bound** | +| Modern/legacy config alias (`mightyetl.connectors.qlik-sense.enabled`) | **Not mirrored** | | Reference SPI class | `QlikSenseTargetConnector` remains as non-registered design/reference code | -| Config binding (`xtrmetl.connectors.qlik-sense.*`) | Historical/reference surface only; not a production capability claim | | Qlik reload API client | **No** | | Qlik data-file upload/import client | **No** | | Direct QVD writer | **No** | @@ -20,7 +21,7 @@ Integrate mightyETL with Qlik Sense / Qlik Cloud only through a product boundary ## Product boundary -The previous scaffold modeled Qlik as a `TargetConnector.write(...)` row sink even though it never implemented a Qlik client and always refused writes. That shape is no longer advertised through the production registry. +The previous scaffold modeled Qlik as a `TargetConnector.write(...)` row sink even though it never implemented a Qlik client and always refused writes. That shape is no longer advertised through the production registry, `ConnectorProperties`, or the supported modern/legacy connector-enable alias list. Current Qlik Cloud APIs expose product-specific operations such as application reload orchestration and data-file upload/import. A future mightyETL integration should therefore be designed explicitly around one of those supported boundaries instead of pretending that Qlik is a transactional warehouse row sink. @@ -29,14 +30,16 @@ Current Qlik Cloud APIs expose product-specific operations such as application r 1. **Warehouse + reload orchestration:** mightyETL lands data in a supported warehouse or database, then a separately designed Qlik integration triggers and observes an application reload. 2. **Data-file workflow:** mightyETL produces a governed file artifact and uses Qlik's supported data-file APIs before a reload, with explicit idempotency, authentication, tenancy, retry, audit, and cleanup contracts. -Neither path is implemented by the current `TargetConnector` SPI. A future implementation requires a separate PRD/TRD/API contract and realistic integration tests before production registration. +Neither path is implemented by the current `TargetConnector` SPI. A future implementation requires a separate PRD/TRD/API contract and realistic integration tests before production registration or configuration binding. -## Historical configuration surface +## Historical reference keys -The reference class still documents the historical keys `tenant-url`, `api-key`, `app-id`, and optional `mode`. Those keys do not grant production support while the connector is absent from `TargetConnectorRegistry`. +The non-registered reference class documents historical design keys such as `tenant-url`, `api-key`, `app-id`, and optional `mode`. Those names are reference material only: Spring `ConnectorProperties` no longer binds a Qlik subtree and the product alias bridge no longer mirrors a Qlik enable flag. + +A future Qlik integration must define a new supported configuration contract from the selected API boundary rather than silently reviving these historical names. ## Limitations - No Qlik API client or SDK integration is shipped. - No reload automation, file upload/import workflow, QVD writer, or tenant-aware Qlik execution exists. -- Qlik must remain absent from production connector discovery until an actual supported integration path has been implemented and verified. +- Qlik must remain absent from production connector discovery and production config binding until an actual supported integration path has been implemented and verified. From 585b7f9439a9a5a3cf959bb10008d620424b4578 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 9 Aug 2026 21:55:40 +0900 Subject: [PATCH 11/15] docs(connectors): record Qlik configuration retirement --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45b08b8c..d12ff391 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Qlik Sense is no longer published as a mightyETL row-write target: it is removed from the production target registry/catalog, Spring `ConnectorProperties`, and the modern/legacy Qlik enable alias. `QlikSenseTargetConnector` remains only non-registered reference/design code until a separately reviewed Qlik reload/data-file integration is implemented and verified. - Durable `POST /api/etl/jobs` submissions now return RFC 9110 `202 Accepted`, a stable pending-job representation, `Location` status-monitor metadata, and explicit replay metadata without changing the synchronous `/api/etl/process` contract. The incomplete intake controller is fail-closed and requires explicit `xtrmetl.etl.jobs.intake-enabled=true` operator opt-in until worker execution and terminal payload clearing are implemented. - Concurrent requests using the same authenticated-principal-scoped semantic idempotency key now return immediate RFC 9457 `409 etl_idempotency_request_in_progress` responses through PostgreSQL `pg_try_advisory_xact_lock`; retries after completion still replay the committed response. - `POST /api/etl/process` now supports optional authenticated-principal-scoped `Idempotency-Key` retries with atomic target writes, durable response replay, payload-conflict rejection, and explicit replay response metadata. @@ -27,14 +28,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - ETL problem-details client and operator contract: `docs/api/problem-details.md`. - Operator-configurable ETL admission limits under `mightyetl.etl.*` / `xtrmetl.etl.*`, backed by `ETL_MAX_PAYLOAD_BYTES` and `ETL_MAX_BATCH_RECORDS` environment variables with hard safety ceilings. - ETL transaction rollback integration coverage and the operator runbook `docs/etl/bounded-atomic-batches.md`. -- Connector scaffolds (contracts + docs only): Qlik Sense, Databricks, Snowflake under `docs/connectors/` and `etl-service` SPI stubs. +- Connector scaffold/reference contracts and docs: Databricks, Snowflake, and the non-registered Qlik design reference under `docs/connectors/` and `etl-service` SPI code. - Any-to-any CDC design notes and source SPI scaffold: `docs/cdc/any-to-any-cdc.md`, `cdc-service` SPI stubs. - CDC operations notes: `docs/cdc/ops-and-reliability.md`. - Product upgrade progress tracker: `docs/mightyETL-product-upgrade-progress.md`. - CDC status/sources API: `GET /api/cdc/status`, `GET /api/cdc/sources` (no secrets). - `DebeziumChangeRecordMapper` + `CanonicalChangeRecord` (mapper unit-tested; not on live publish path). - CDC target SPI registry (`kafka`, `jdbc-replica`) for any-to-any routing scaffold. -- `etl-service` `xtrmetl.connectors.*` disabled config keys for Databricks/Snowflake/Qlik. +- `etl-service` `xtrmetl.connectors.*` disabled config keys for the still-registered Databricks and Snowflake scaffolds. - Dual-read config aliases: `mightyetl.*` preferred → `xtrmetl.*` (`MightyEtlConfigAliasEnvironmentPostProcessor`). - Configurable replica tables (`xtrmetl.replica.tables`) for `(id,data)`-shaped tables. - Optional CDC canonical-map counters (`xtrmetl.cdc.canonical-map-enabled`). From ad47e73acdb314853671f4d6055f6a92e7d3963f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 9 Aug 2026 23:42:09 +0900 Subject: [PATCH 12/15] test(connectors): guard Qlik reference lifecycle wording --- .../QlikReferenceDocumentationTest.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 etl-service/src/test/java/com/xtrmetl/etl/connector/QlikReferenceDocumentationTest.java diff --git a/etl-service/src/test/java/com/xtrmetl/etl/connector/QlikReferenceDocumentationTest.java b/etl-service/src/test/java/com/xtrmetl/etl/connector/QlikReferenceDocumentationTest.java new file mode 100644 index 00000000..22d7aa48 --- /dev/null +++ b/etl-service/src/test/java/com/xtrmetl/etl/connector/QlikReferenceDocumentationTest.java @@ -0,0 +1,32 @@ +package com.xtrmetl.etl.connector; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Keeps the retained Qlik reference type aligned with its retired production lifecycle status. + */ +class QlikReferenceDocumentationTest { + + @Test + void retainedReferenceTypeDoesNotClaimShippedProductionHooks() throws IOException { + String source = Files.readString( + Path.of( + "src/main/java/com/xtrmetl/etl/connector/", + "QlikSenseTargetConnector.java" + ), + StandardCharsets.UTF_8 + ); + + assertTrue(source.contains("reference-only design type")); + assertTrue(source.contains("not registered in the production target registry")); + assertFalse(source.contains("Ships config contract + validation + catalog hooks")); + } +} From 99e848edae3142be5fd60f074ee4cce947f2b86a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 9 Aug 2026 23:43:46 +0900 Subject: [PATCH 13/15] docs(connectors): mark Qlik class reference-only --- .../etl/connector/QlikSenseTargetConnector.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/etl-service/src/main/java/com/xtrmetl/etl/connector/QlikSenseTargetConnector.java b/etl-service/src/main/java/com/xtrmetl/etl/connector/QlikSenseTargetConnector.java index e89866c6..8684fa58 100644 --- a/etl-service/src/main/java/com/xtrmetl/etl/connector/QlikSenseTargetConnector.java +++ b/etl-service/src/main/java/com/xtrmetl/etl/connector/QlikSenseTargetConnector.java @@ -4,10 +4,16 @@ import java.util.Map; /** - * Qlik Sense / Qlik Cloud target surface (reload-trigger first). + * Reference-only design type for a possible future Qlik Sense / Qlik Cloud integration. * - *

Ships config contract + validation + catalog hooks. No Qlik REST client wired; - * {@link #write} always refuses (SCAFFOLD).

+ *

This type is not registered in the production target registry and has no Spring-bound + * production configuration. It is retained only to preserve an explicit design reference while + * mightyETL has no supported Qlik row-write, reload, or data-file execution path. As a scaffold, + * {@link #write} always refuses execution.

+ * + *

A production Qlik integration requires a separately reviewed reload/data-file contract, + * maintained client implementation, credential boundary, lifecycle semantics, operator guidance, + * and realistic integration evidence before this type may become production-discoverable.

* * @see docs/connectors/qlik-sense.md */ From f04512c4cce2f0c1d33e74286cda2628050784b9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 9 Aug 2026 23:47:18 +0900 Subject: [PATCH 14/15] test(connectors): preserve exact Qlik lifecycle wording --- .../xtrmetl/etl/connector/QlikReferenceDocumentationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etl-service/src/test/java/com/xtrmetl/etl/connector/QlikReferenceDocumentationTest.java b/etl-service/src/test/java/com/xtrmetl/etl/connector/QlikReferenceDocumentationTest.java index 22d7aa48..d79201dd 100644 --- a/etl-service/src/test/java/com/xtrmetl/etl/connector/QlikReferenceDocumentationTest.java +++ b/etl-service/src/test/java/com/xtrmetl/etl/connector/QlikReferenceDocumentationTest.java @@ -25,7 +25,7 @@ void retainedReferenceTypeDoesNotClaimShippedProductionHooks() throws IOExceptio StandardCharsets.UTF_8 ); - assertTrue(source.contains("reference-only design type")); + assertTrue(source.contains("Reference-only design type")); assertTrue(source.contains("not registered in the production target registry")); assertFalse(source.contains("Ships config contract + validation + catalog hooks")); } From 790a80951c736ef5082b2dce1c0494413a456326 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 11 Sep 2026 12:42:49 +0900 Subject: [PATCH 15/15] test(config): drop retired Qlik keys from alias surface fixture --- .../MightyEtlConfigAliasEnvironmentPostProcessorTest.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/etl-service/src/test/java/com/xtrmetl/etl/config/MightyEtlConfigAliasEnvironmentPostProcessorTest.java b/etl-service/src/test/java/com/xtrmetl/etl/config/MightyEtlConfigAliasEnvironmentPostProcessorTest.java index 76ea2b40..47cc2a91 100644 --- a/etl-service/src/test/java/com/xtrmetl/etl/config/MightyEtlConfigAliasEnvironmentPostProcessorTest.java +++ b/etl-service/src/test/java/com/xtrmetl/etl/config/MightyEtlConfigAliasEnvironmentPostProcessorTest.java @@ -46,12 +46,7 @@ class MightyEtlConfigAliasEnvironmentPostProcessorTest { Map.entry("connectors.snowflake.private-key", "test-private-key"), Map.entry("connectors.snowflake.role", "ETL_ROLE"), Map.entry("connectors.snowflake.table", "EVENTS"), - Map.entry("connectors.snowflake.merge-keys", "id"), - Map.entry("connectors.qlik-sense.enabled", "true"), - Map.entry("connectors.qlik-sense.tenant-url", "https://tenant.example"), - Map.entry("connectors.qlik-sense.api-key", "test-qlik-api-key"), - Map.entry("connectors.qlik-sense.app-id", "test-app-id"), - Map.entry("connectors.qlik-sense.mode", "reload-only") + Map.entry("connectors.snowflake.merge-keys", "id") ); @Test @@ -59,7 +54,6 @@ void aliasAllowListMatchesCompleteConnectorBindingSurface() { Set expected = new TreeSet<>(); expected.addAll(writableConnectorKeys("databricks", ConnectorProperties.DatabricksProps.class)); expected.addAll(writableConnectorKeys("snowflake", ConnectorProperties.SnowflakeProps.class)); - expected.addAll(writableConnectorKeys("qlik-sense", ConnectorProperties.QlikSenseProps.class)); Set actual = new TreeSet<>(); MightyEtlConfigAliasEnvironmentPostProcessor.RELATIVE_KEYS.stream()