diff --git a/CHANGELOG.md b/CHANGELOG.md
index 45b08b8c..b692ca66 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
+- The durable-job JaCoCo gate now selects real compiled production classes per report/check goal and fails closed when the selected bundle is empty, preventing zero-class executions from being reported as 100% owned-production coverage evidence.
- 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.
diff --git a/docs/doctoring/jacoco-nonvacuous-coverage.md b/docs/doctoring/jacoco-nonvacuous-coverage.md
new file mode 100644
index 00000000..849473bd
--- /dev/null
+++ b/docs/doctoring/jacoco-nonvacuous-coverage.md
@@ -0,0 +1,96 @@
+# Non-vacuous JaCoCo Coverage Evidence
+
+**Status:** `active_pr` #164
+**Protected baseline assessed:** `develop@622e5e6c3d534f230c390f10e3832efadfc01825`
+**Assessment date:** 2026-08-09
+
+## Incident evidence
+
+PR #155 CI `31314123991`, macOS job `93246494460`, ran the full Maven reactor and emitted:
+
+```text
+--- jacoco:0.8.15:report (report-durable-job-coverage) @ etl-service ---
+Analyzed bundle 'etl-service' with 0 classes
+
+--- jacoco:0.8.15:check (check-durable-job-coverage) @ etl-service ---
+All coverage checks have been met.
+```
+
+The job had already compiled production/test code and ran the real test suites successfully. The coverage defect is therefore not an empty project or missing test execution: the report/check class selection was empty while the configured zero-missed limits still passed.
+
+That result invalidates the previous use of this JaCoCo execution as evidence of 100% durable-job production coverage. It does not invalidate the test results themselves.
+
+## Root cause
+
+Protected `etl-service/pom.xml` configured dotted patterns at plugin scope:
+
+```text
+com.xtrmetl.etl.job.*
+com.xtrmetl.etl.controller.EtlJobController*
+```
+
+JaCoCo `prepare-agent` defines `includes` as class names. `CoverageTransformer` converts those configured names to VM notation before matching loaded classes.
+
+JaCoCo Maven `report` and `check`, however, define their `includes` as **class files**. `ReportSupport` constructs a Maven `FileFilter` and enumerates matching files below the compiled classes directory before analysis. The plugin-level configuration therefore reused one syntax across execution-time class names and report/check class-file paths.
+
+The second root cause is control design: every existing coverage limit constrained `MISSEDCOUNT` to zero, but none required the selected bundle to contain a class. An empty bundle can therefore satisfy the limits vacuously.
+
+## Selected repair
+
+The active repair separates the goals:
+
+1. `prepare-agent` receives no restrictive include filter; JaCoCo documents this filter as unnecessary except for technical/performance corner cases.
+2. `report-durable-job-coverage` selects compiled files using:
+ - `com/xtrmetl/etl/job/*.class`
+ - `com/xtrmetl/etl/controller/EtlJobController*.class`
+3. `check-durable-job-coverage` uses the same class-file paths.
+4. The check applies limits to the selected `BUNDLE` and first requires `CLASS TOTALCOUNT >= 1`.
+5. Exact zero missed INSTRUCTION, LINE, METHOD and BRANCH counters remain unchanged in strictness.
+
+If this exposes real uncovered production code, the correct repair is additional realistic tests or a reviewed product-code removal/refactor—not reintroducing an empty filter or lowering the thresholds.
+
+## TDD
+
+Fail-first commit `ba174ac98128da254358a5f8dccdfcf8eee93496` adds `JaCoCoCoverageConfigurationTest` before changing the POM. The test requires:
+
+- plugin-level includes absent;
+- separate report/check class-file patterns;
+- a non-empty BUNDLE guard;
+- actual compiled `EtlJobService.class` and `EtlJobController.class` at test runtime.
+
+Protected POM fails the configuration contract while the named production target classes exist, so the RED reaches the intended quality-gate boundary rather than a missing fixture.
+
+The first GREEN candidate is `b65c31adcb636640a1a9a44c5b80eec2a487215c`, which changes only JaCoCo goal configuration after the fail-first test.
+
+## Acceptance evidence
+
+The final exact head is not accepted until a fresh hosted run proves all of the following:
+
+- JaCoCo report logs a nonzero analyzed-class count for `etl-service`;
+- the generated report includes the intended durable-job classes;
+- JaCoCo check satisfies `CLASS TOTALCOUNT >= 1`;
+- selected owned production code has zero missed instruction, line, method and branch counters;
+- full Maven reactor and supported hosted OS tests succeed;
+- any real coverage deficits revealed by the repaired selector are fixed test-first;
+- Dependency Review, SBOM, SAST/security and review gates pass;
+- current protected synthetic-merge execution is not mislabeled literal-source proof.
+
+## Rollback
+
+Reinstating the old plugin-level dotted include patterns is not an acceptable rollback because it restores a proven vacuous coverage gate. If the new selector exposes too much or the target definition is wrong, revise the explicit class-file target under review while retaining the non-empty class-count invariant.
+
+## References — APA 7th
+
+JaCoCo. (2026). *Java agent*. https://www.jacoco.org/jacoco/trunk/doc/agent.html
+
+JaCoCo. (2026). *jacoco:prepare-agent*. https://www.jacoco.org/jacoco/trunk/doc/prepare-agent-mojo.html
+
+JaCoCo. (2026). *jacoco:report*. https://www.jacoco.org/jacoco/trunk/doc/report-mojo.html
+
+JaCoCo. (2026). *jacoco:check*. https://www.jacoco.org/jacoco/trunk/doc/check-mojo.html
+
+JaCoCo. (2026). *CoverageTransformer.java*. https://www.jacoco.org/jacoco/trunk/coverage/org.jacoco.agent.rt/org.jacoco.agent.rt.internal/CoverageTransformer.java.html
+
+JaCoCo. (2026). *ReportSupport.java*. https://www.jacoco.org/jacoco/trunk/coverage/jacoco-maven-plugin/org.jacoco.maven/ReportSupport.java.html
+
+JaCoCo. (2026). *FileFilter.java*. https://www.jacoco.org/jacoco/trunk/coverage/jacoco-maven-plugin/org.jacoco.maven/FileFilter.java.html
diff --git a/etl-service/pom.xml b/etl-service/pom.xml
index 00695de2..3d840a28 100644
--- a/etl-service/pom.xml
+++ b/etl-service/pom.xml
@@ -98,12 +98,6 @@
org.jacoco
jacoco-maven-plugin
0.8.15
-
-
- com.xtrmetl.etl.job.*
- com.xtrmetl.etl.controller.EtlJobController*
-
-
prepare-durable-job-coverage
@@ -118,6 +112,12 @@
report
+
+
+ com/xtrmetl/etl/job/*.class
+ com/xtrmetl/etl/controller/EtlJobController*.class
+
+
check-durable-job-coverage
@@ -126,14 +126,19 @@
check
+
+ com/xtrmetl/etl/job/*.class
+ com/xtrmetl/etl/controller/EtlJobController*.class
+
- CLASS
-
- com.xtrmetl.etl.job.*
- com.xtrmetl.etl.controller.EtlJobController*
-
+ BUNDLE
+
+ CLASS
+ TOTALCOUNT
+ 1
+
INSTRUCTION
MISSEDCOUNT
diff --git a/etl-service/src/test/java/com/xtrmetl/etl/documentation/JaCoCoCoverageConfigurationTest.java b/etl-service/src/test/java/com/xtrmetl/etl/documentation/JaCoCoCoverageConfigurationTest.java
new file mode 100644
index 00000000..88043151
--- /dev/null
+++ b/etl-service/src/test/java/com/xtrmetl/etl/documentation/JaCoCoCoverageConfigurationTest.java
@@ -0,0 +1,185 @@
+package com.xtrmetl.etl.documentation;
+
+import org.junit.jupiter.api.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Guards the JaCoCo durable-job coverage gate against an empty production-class selection that can
+ * otherwise satisfy zero-missed counters vacuously.
+ */
+class JaCoCoCoverageConfigurationTest {
+
+ private static final Path PROJECT_ROOT = projectRoot();
+ private static final Set EXPECTED_CLASS_FILE_PATTERNS = Set.of(
+ "com/xtrmetl/etl/job/*.class",
+ "com/xtrmetl/etl/controller/EtlJobController*.class"
+ );
+
+ @Test
+ void reportAndCheckSelectCompiledClassFilesInsteadOfReusingAgentClassNames() throws Exception {
+ Element plugin = jacocoPlugin(parsePom());
+ Element pluginConfiguration = directChild(plugin, "configuration");
+ assertTrue(
+ pluginConfiguration == null || directChild(pluginConfiguration, "includes") == null,
+ "Plugin-level includes leak one filter syntax into prepare-agent, report, and check"
+ );
+
+ assertEquals(
+ EXPECTED_CLASS_FILE_PATTERNS,
+ executionIncludes(plugin, "report-durable-job-coverage")
+ );
+ assertEquals(
+ EXPECTED_CLASS_FILE_PATTERNS,
+ executionIncludes(plugin, "check-durable-job-coverage")
+ );
+ }
+
+ @Test
+ void coverageCheckRequiresAtLeastOneAnalyzedProductionClass() throws Exception {
+ Element plugin = jacocoPlugin(parsePom());
+ Element checkExecution = execution(plugin, "check-durable-job-coverage");
+ Element configuration = requireDirectChild(checkExecution, "configuration");
+ Element rules = requireDirectChild(configuration, "rules");
+ Element rule = requireDirectChild(rules, "rule");
+
+ assertEquals("BUNDLE", directChildText(rule, "element"));
+
+ Element limits = requireDirectChild(rule, "limits");
+ boolean foundNonEmptyGuard = false;
+ for (Element limit : directChildren(limits, "limit")) {
+ if ("CLASS".equals(directChildText(limit, "counter"))
+ && "TOTALCOUNT".equals(directChildText(limit, "value"))
+ && "1".equals(directChildText(limit, "minimum"))) {
+ foundNonEmptyGuard = true;
+ }
+ }
+ assertTrue(
+ foundNonEmptyGuard,
+ "Coverage must fail closed when the selected production bundle contains zero classes"
+ );
+ }
+
+ @Test
+ void intendedCoverageTargetContainsCompiledProductionClasses() {
+ Path classes = PROJECT_ROOT.resolve("etl-service/target/classes");
+ assertTrue(
+ Files.isRegularFile(classes.resolve("com/xtrmetl/etl/job/EtlJobService.class")),
+ "EtlJobService must be a real compiled production coverage target"
+ );
+ assertTrue(
+ Files.isRegularFile(classes.resolve("com/xtrmetl/etl/controller/EtlJobController.class")),
+ "EtlJobController must be a real compiled production coverage target"
+ );
+ }
+
+ private static Document parsePom() throws Exception {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+ factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
+ factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+ factory.setXIncludeAware(false);
+ factory.setExpandEntityReferences(false);
+ return factory.newDocumentBuilder().parse(PROJECT_ROOT.resolve("etl-service/pom.xml").toFile());
+ }
+
+ private static Element jacocoPlugin(Document document) {
+ NodeList plugins = document.getElementsByTagName("plugin");
+ for (int index = 0; index < plugins.getLength(); index++) {
+ Element plugin = (Element) plugins.item(index);
+ if ("org.jacoco".equals(directChildText(plugin, "groupId"))
+ && "jacoco-maven-plugin".equals(directChildText(plugin, "artifactId"))) {
+ return plugin;
+ }
+ }
+ throw new AssertionError("etl-service POM is missing jacoco-maven-plugin");
+ }
+
+ private static Set executionIncludes(Element plugin, String executionId) {
+ Element configuration = requireDirectChild(execution(plugin, executionId), "configuration");
+ Element includes = requireDirectChild(configuration, "includes");
+ Set values = new LinkedHashSet<>();
+ for (Element include : directChildren(includes, "include")) {
+ values.add(include.getTextContent().trim());
+ }
+ assertFalse(values.isEmpty(), "JaCoCo class-file selection must not be empty");
+ return values;
+ }
+
+ private static Element execution(Element plugin, String executionId) {
+ Element executions = requireDirectChild(plugin, "executions");
+ for (Element execution : directChildren(executions, "execution")) {
+ if (executionId.equals(directChildText(execution, "id"))) {
+ return execution;
+ }
+ }
+ throw new AssertionError("Missing JaCoCo execution: " + executionId);
+ }
+
+ private static Element requireDirectChild(Element parent, String name) {
+ Element child = directChild(parent, name);
+ assertNotNull(child, () -> "Missing <" + name + "> under <" + parent.getTagName() + ">");
+ return child;
+ }
+
+ private static Element directChild(Element parent, String name) {
+ NodeList children = parent.getChildNodes();
+ for (int index = 0; index < children.getLength(); index++) {
+ Node child = children.item(index);
+ if (child instanceof Element element && name.equals(element.getTagName())) {
+ return element;
+ }
+ }
+ return null;
+ }
+
+ private static Set directChildren(Element parent, String name) {
+ Set matches = new LinkedHashSet<>();
+ NodeList children = parent.getChildNodes();
+ for (int index = 0; index < children.getLength(); index++) {
+ Node child = children.item(index);
+ if (child instanceof Element element && name.equals(element.getTagName())) {
+ matches.add(element);
+ }
+ }
+ return matches;
+ }
+
+ private static String directChildText(Element parent, String name) {
+ Element child = directChild(parent, name);
+ return child == null ? null : child.getTextContent().trim();
+ }
+
+ /** Finds the repository root from root- or module-scoped Maven execution. */
+ private static Path projectRoot() {
+ Path current = Paths.get(System.getProperty("user.dir")).toAbsolutePath();
+ Path lastPomParent = null;
+ while (current != null) {
+ if (Files.exists(current.resolve(".git"))) {
+ return current;
+ }
+ if (Files.exists(current.resolve("pom.xml"))) {
+ lastPomParent = current;
+ }
+ current = current.getParent();
+ }
+ if (lastPomParent != null) {
+ return lastPomParent;
+ }
+ throw new IllegalStateException("Could not find project root");
+ }
+}
diff --git a/etl-service/src/test/java/com/xtrmetl/etl/documentation/JaCoCoCoverageRuleCoLocationTest.java b/etl-service/src/test/java/com/xtrmetl/etl/documentation/JaCoCoCoverageRuleCoLocationTest.java
new file mode 100644
index 00000000..4ba745f7
--- /dev/null
+++ b/etl-service/src/test/java/com/xtrmetl/etl/documentation/JaCoCoCoverageRuleCoLocationTest.java
@@ -0,0 +1,152 @@
+package com.xtrmetl.etl.documentation;
+
+import org.junit.jupiter.api.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+/**
+ * Verifies that the non-vacuity guard and every zero-missed JaCoCo threshold remain co-located in
+ * the same {@code BUNDLE} rule. Keeping the limits together prevents a future refactor from leaving
+ * the non-empty guard on one rule while silently moving coverage thresholds elsewhere.
+ */
+class JaCoCoCoverageRuleCoLocationTest {
+
+ private static final Set REQUIRED_LIMITS = Set.of(
+ "CLASS|TOTALCOUNT|min=1",
+ "INSTRUCTION|MISSEDCOUNT|max=0",
+ "LINE|MISSEDCOUNT|max=0",
+ "METHOD|MISSEDCOUNT|max=0",
+ "BRANCH|MISSEDCOUNT|max=0"
+ );
+
+ @Test
+ void durableJobCoverageKeepsEveryRequiredLimitInOneBundleRule() throws Exception {
+ Element plugin = jacocoPlugin(parsePom());
+ Element execution = execution(plugin, "check-durable-job-coverage");
+ Element rules = requireDirectChild(requireDirectChild(execution, "configuration"), "rules");
+ Element bundleRule = null;
+ for (Element rule : directChildren(rules, "rule")) {
+ if ("BUNDLE".equals(directChildText(rule, "element"))) {
+ bundleRule = rule;
+ break;
+ }
+ }
+ assertNotNull(bundleRule, "Coverage check must contain a BUNDLE rule");
+
+ Set actualLimits = new LinkedHashSet<>();
+ for (Element limit : directChildren(requireDirectChild(bundleRule, "limits"), "limit")) {
+ String counter = directChildText(limit, "counter");
+ String value = directChildText(limit, "value");
+ String minimum = directChildText(limit, "minimum");
+ String maximum = directChildText(limit, "maximum");
+ if (minimum != null) {
+ actualLimits.add(counter + "|" + value + "|min=" + minimum);
+ }
+ if (maximum != null) {
+ actualLimits.add(counter + "|" + value + "|max=" + maximum);
+ }
+ }
+
+ assertEquals(
+ REQUIRED_LIMITS,
+ actualLimits,
+ "The BUNDLE rule must keep its non-empty guard and exact zero-missed thresholds together"
+ );
+ }
+
+ private static Document parsePom() throws Exception {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+ factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
+ factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+ factory.setXIncludeAware(false);
+ factory.setExpandEntityReferences(false);
+ return factory.newDocumentBuilder().parse(projectRoot().resolve("etl-service/pom.xml").toFile());
+ }
+
+ private static Element jacocoPlugin(Document document) {
+ NodeList plugins = document.getElementsByTagName("plugin");
+ for (int index = 0; index < plugins.getLength(); index++) {
+ Element plugin = (Element) plugins.item(index);
+ if ("org.jacoco".equals(directChildText(plugin, "groupId"))
+ && "jacoco-maven-plugin".equals(directChildText(plugin, "artifactId"))) {
+ return plugin;
+ }
+ }
+ throw new AssertionError("etl-service POM is missing jacoco-maven-plugin");
+ }
+
+ private static Element execution(Element plugin, String executionId) {
+ for (Element execution : directChildren(requireDirectChild(plugin, "executions"), "execution")) {
+ if (executionId.equals(directChildText(execution, "id"))) {
+ return execution;
+ }
+ }
+ throw new AssertionError("Missing JaCoCo execution: " + executionId);
+ }
+
+ private static Element requireDirectChild(Element parent, String name) {
+ Element child = directChild(parent, name);
+ assertNotNull(child, () -> "Missing <" + name + "> under <" + parent.getTagName() + ">");
+ return child;
+ }
+
+ private static Element directChild(Element parent, String name) {
+ NodeList children = parent.getChildNodes();
+ for (int index = 0; index < children.getLength(); index++) {
+ Node child = children.item(index);
+ if (child instanceof Element element && name.equals(element.getTagName())) {
+ return element;
+ }
+ }
+ return null;
+ }
+
+ private static Set directChildren(Element parent, String name) {
+ Set matches = new LinkedHashSet<>();
+ NodeList children = parent.getChildNodes();
+ for (int index = 0; index < children.getLength(); index++) {
+ Node child = children.item(index);
+ if (child instanceof Element element && name.equals(element.getTagName())) {
+ matches.add(element);
+ }
+ }
+ return matches;
+ }
+
+ private static String directChildText(Element parent, String name) {
+ Element child = directChild(parent, name);
+ return child == null ? null : child.getTextContent().trim();
+ }
+
+ /** Finds the repository root from root- or module-scoped Maven execution. */
+ private static Path projectRoot() {
+ Path current = Paths.get(System.getProperty("user.dir")).toAbsolutePath();
+ Path lastPomParent = null;
+ while (current != null) {
+ if (Files.exists(current.resolve(".git"))) {
+ return current;
+ }
+ if (Files.exists(current.resolve("pom.xml"))) {
+ lastPomParent = current;
+ }
+ current = current.getParent();
+ }
+ if (lastPomParent != null) {
+ return lastPomParent;
+ }
+ throw new IllegalStateException("Could not find project root");
+ }
+}
diff --git a/etl-service/src/test/java/com/xtrmetl/etl/job/EtlJobCoveragePolicyTest.java b/etl-service/src/test/java/com/xtrmetl/etl/job/EtlJobCoveragePolicyTest.java
index 4a728d75..d520b43e 100644
--- a/etl-service/src/test/java/com/xtrmetl/etl/job/EtlJobCoveragePolicyTest.java
+++ b/etl-service/src/test/java/com/xtrmetl/etl/job/EtlJobCoveragePolicyTest.java
@@ -8,19 +8,22 @@
import java.nio.file.Path;
import java.nio.file.Paths;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
- * Keeps the durable-job production slice bound to an executable 100% coverage policy.
+ * Keeps the durable-job production slice bound to an executable, non-vacuous 100% coverage policy.
*
* The policy is intentionally scoped to the production classes introduced by the durable-job
- * intake slice. It requires current Java-compatible JaCoCo instrumentation and zero missed
- * instructions, lines, methods, or branches while the ordinary {@code mvn test} lifecycle runs.
+ * intake slice. It requires current Java-compatible JaCoCo instrumentation, a non-empty selected
+ * production bundle, and zero missed instructions, lines, methods, or branches while the ordinary
+ * {@code mvn test} lifecycle runs.
*/
class EtlJobCoveragePolicyTest {
/**
- * Requires the ETL module build to fail when any durable-job production path is untested.
+ * Requires the ETL module build to fail when the selected durable-job bundle is empty or any
+ * selected durable-job production path is untested.
*
* @throws IOException when the module build descriptor cannot be read
*/
@@ -36,10 +39,18 @@ void etlModuleEnforcesCompleteInstructionAndBranchCoverageForTheDurableJobSlice(
assertTrue(modulePom.contains("test"));
assertTrue(modulePom.contains("report"));
assertTrue(modulePom.contains("check"));
- assertTrue(modulePom.contains("com.xtrmetl.etl.job.*"));
+ assertTrue(modulePom.contains("com/xtrmetl/etl/job/*.class"));
assertTrue(modulePom.contains(
+ "com/xtrmetl/etl/controller/EtlJobController*.class"
+ ));
+ assertFalse(modulePom.contains("com.xtrmetl.etl.job.*"));
+ assertFalse(modulePom.contains(
"com.xtrmetl.etl.controller.EtlJobController*"
));
+ assertTrue(modulePom.contains("BUNDLE"));
+ assertTrue(modulePom.contains("CLASS"));
+ assertTrue(modulePom.contains("TOTALCOUNT"));
+ assertTrue(modulePom.contains("1"));
assertTrue(modulePom.contains("INSTRUCTION"));
assertTrue(modulePom.contains("LINE"));
assertTrue(modulePom.contains("METHOD"));
diff --git a/etl-service/src/test/java/com/xtrmetl/etl/job/EtlJobServiceBoundaryTest.java b/etl-service/src/test/java/com/xtrmetl/etl/job/EtlJobServiceBoundaryTest.java
index 6ce140d6..2d254b0b 100644
--- a/etl-service/src/test/java/com/xtrmetl/etl/job/EtlJobServiceBoundaryTest.java
+++ b/etl-service/src/test/java/com/xtrmetl/etl/job/EtlJobServiceBoundaryTest.java
@@ -10,10 +10,18 @@
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.support.TransactionSynchronizationManager;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.Security;
+import java.util.HashMap;
+import java.util.Map;
import java.util.UUID;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -69,6 +77,44 @@ void returnsImmediateConflictWhenAnotherSubmissionOwnsTheTryLock() {
verifyNoInteractions(jdbcTemplate);
}
+ @Test
+ void reportsPlatformFailureWhenRequiredSha256DigestIsUnavailable() {
+ JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class);
+ EtlRequestLock requestLock = mock(EtlRequestLock.class);
+ EtlJobService service = service(jdbcTemplate, requestLock, new EtlBatchProperties());
+ Provider[] originalProviders = Security.getProviders();
+ Provider[] sha256Providers = Security.getProviders("MessageDigest.SHA-256");
+ assertNotNull(sha256Providers);
+ assertTrue(sha256Providers.length > 0);
+
+ Map originalPositions = new HashMap<>();
+ for (int index = 0; index < originalProviders.length; index++) {
+ originalPositions.put(originalProviders[index].getName(), index + 1);
+ }
+
+ try {
+ for (Provider provider : sha256Providers) {
+ Security.removeProvider(provider.getName());
+ }
+ assertThrows(NoSuchAlgorithmException.class, () -> MessageDigest.getInstance("SHA-256"));
+
+ IllegalStateException exception = assertThrows(
+ IllegalStateException.class,
+ () -> service.findOwned(UUID.randomUUID(), "tenant_alpha")
+ );
+
+ assertEquals("SHA-256 is required by the Java platform", exception.getMessage());
+ assertEquals(NoSuchAlgorithmException.class, exception.getCause().getClass());
+ } finally {
+ for (Provider provider : originalProviders) {
+ if (Security.getProvider(provider.getName()) == null) {
+ Security.insertProviderAt(provider, originalPositions.get(provider.getName()));
+ }
+ }
+ }
+ verifyNoInteractions(requestLock, jdbcTemplate);
+ }
+
@Test
void rejectsInvalidKeysAndPrincipalScopesBeforeTransactionOrDatabaseAccess() {
JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class);
@@ -110,6 +156,10 @@ void rejectsEveryCoveredPayloadAdmissionFailureBeforePersistence() {
EtlRequestError.INVALID_JSON,
() -> service.submit(null, IDEMPOTENCY_KEY, "tenant_alpha")
);
+ assertError(
+ EtlRequestError.INVALID_JSON,
+ () -> service.submit(" ", IDEMPOTENCY_KEY, "tenant_alpha")
+ );
assertError(
EtlRequestError.INVALID_JSON,
() -> service.submit("null", IDEMPOTENCY_KEY, "tenant_alpha")
@@ -154,10 +204,50 @@ void rejectsEveryCoveredPayloadAdmissionFailureBeforePersistence() {
EtlRequestError.INVALID_RECORD,
() -> service.submit("[{\"id\":\" record_alpha\"}]", IDEMPOTENCY_KEY, "tenant_alpha")
);
+ assertError(
+ EtlRequestError.INVALID_RECORD,
+ () -> service.submit(
+ payloadWithIdentifier("\u00a0record_alpha"),
+ IDEMPOTENCY_KEY,
+ "tenant_alpha"
+ )
+ );
+ assertError(
+ EtlRequestError.INVALID_RECORD,
+ () -> service.submit(
+ payloadWithIdentifier("x".repeat(257)),
+ IDEMPOTENCY_KEY,
+ "tenant_alpha"
+ )
+ );
assertError(
EtlRequestError.INVALID_RECORD,
() -> service.submit("[{\"id\":\"record\\u0000alpha\"}]", IDEMPOTENCY_KEY, "tenant_alpha")
);
+ assertError(
+ EtlRequestError.INVALID_RECORD,
+ () -> service.submit(
+ payloadWithIdentifier("record" + Character.toString(0x200e) + "alpha"),
+ IDEMPOTENCY_KEY,
+ "tenant_alpha"
+ )
+ );
+ assertError(
+ EtlRequestError.INVALID_RECORD,
+ () -> service.submit(
+ payloadWithIdentifier("record" + Character.toString(0x2028) + "alpha"),
+ IDEMPOTENCY_KEY,
+ "tenant_alpha"
+ )
+ );
+ assertError(
+ EtlRequestError.INVALID_RECORD,
+ () -> service.submit(
+ payloadWithIdentifier("record" + Character.toString(0x2029) + "alpha"),
+ IDEMPOTENCY_KEY,
+ "tenant_alpha"
+ )
+ );
assertError(
EtlRequestError.INVALID_RECORD,
() -> service.submit(
@@ -203,6 +293,10 @@ void validatesConstructorAndLookupRequiredValues() {
verifyNoInteractions(requestLock, jdbcTemplate);
}
+ private static String payloadWithIdentifier(String identifier) {
+ return "[{\"id\":\"" + identifier + "\"}]";
+ }
+
private static EtlJobService service(
JdbcTemplate jdbcTemplate,
EtlRequestLock requestLock,
diff --git a/etl-service/src/test/java/com/xtrmetl/etl/job/EtlJobServiceDefensiveBranchTest.java b/etl-service/src/test/java/com/xtrmetl/etl/job/EtlJobServiceDefensiveBranchTest.java
new file mode 100644
index 00000000..556eb460
--- /dev/null
+++ b/etl-service/src/test/java/com/xtrmetl/etl/job/EtlJobServiceDefensiveBranchTest.java
@@ -0,0 +1,85 @@
+package com.xtrmetl.etl.job;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.xtrmetl.etl.service.EtlBatchProperties;
+import com.xtrmetl.etl.service.EtlRequestError;
+import com.xtrmetl.etl.service.EtlRequestException;
+import com.xtrmetl.etl.service.EtlRequestLock;
+import org.junit.jupiter.api.Test;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import java.util.Collections;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+/**
+ * Exercises defensive JSON-tree outcomes that ordinary Jackson parsing cannot reliably manufacture.
+ *
+ * The service accepts an application-provided {@link ObjectMapper}, copies it, and therefore owns
+ * the boundary between parser output and durable admission. These tests keep that boundary fail-closed
+ * when a compatible mapper reports end-of-input without a tree or exposes a Java {@code null} array
+ * element instead of Jackson's usual {@code NullNode}. Neither case may reach transaction locks or
+ * persistence.
+ */
+class EtlJobServiceDefensiveBranchTest {
+
+ private static final String IDEMPOTENCY_KEY = "550e8400-e29b-41d4-a716-446655440000";
+ private static final String PRINCIPAL_SCOPE = "tenant_alpha";
+
+ @Test
+ void rejectsParserEndOfInputWhenMapperProducesNoTree() throws Exception {
+ JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class);
+ EtlRequestLock requestLock = mock(EtlRequestLock.class);
+ ObjectMapper sourceMapper = mock(ObjectMapper.class);
+ ObjectMapper copiedMapper = mock(ObjectMapper.class);
+ when(sourceMapper.copy()).thenReturn(copiedMapper);
+ when(copiedMapper.readTree("parser-end-of-input")).thenReturn(null);
+ EtlJobService service = new EtlJobService(
+ jdbcTemplate,
+ sourceMapper,
+ new EtlBatchProperties(),
+ requestLock
+ );
+
+ EtlRequestException exception = assertThrows(
+ EtlRequestException.class,
+ () -> service.submit("parser-end-of-input", IDEMPOTENCY_KEY, PRINCIPAL_SCOPE)
+ );
+
+ assertEquals(EtlRequestError.INVALID_JSON, exception.error());
+ verifyNoInteractions(requestLock, jdbcTemplate);
+ }
+
+ @Test
+ void rejectsNullParserArrayElementBeforeTransactionOrPersistence() throws Exception {
+ JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class);
+ EtlRequestLock requestLock = mock(EtlRequestLock.class);
+ ObjectMapper sourceMapper = mock(ObjectMapper.class);
+ ObjectMapper copiedMapper = mock(ObjectMapper.class);
+ JsonNode root = mock(JsonNode.class);
+ when(sourceMapper.copy()).thenReturn(copiedMapper);
+ when(copiedMapper.readTree("parser-null-array-element")).thenReturn(root);
+ when(root.isArray()).thenReturn(true);
+ when(root.size()).thenReturn(1);
+ when(root.iterator()).thenReturn(Collections.singletonList((JsonNode) null).iterator());
+ EtlJobService service = new EtlJobService(
+ jdbcTemplate,
+ sourceMapper,
+ new EtlBatchProperties(),
+ requestLock
+ );
+
+ EtlRequestException exception = assertThrows(
+ EtlRequestException.class,
+ () -> service.submit("parser-null-array-element", IDEMPOTENCY_KEY, PRINCIPAL_SCOPE)
+ );
+
+ assertEquals(EtlRequestError.INVALID_RECORD, exception.error());
+ verifyNoInteractions(requestLock, jdbcTemplate);
+ }
+}