Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
96 changes: 96 additions & 0 deletions docs/doctoring/jacoco-nonvacuous-coverage.md
Original file line number Diff line number Diff line change
@@ -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
27 changes: 16 additions & 11 deletions etl-service/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,6 @@
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.15</version>
<configuration>
<includes>
<include>com.xtrmetl.etl.job.*</include>
<include>com.xtrmetl.etl.controller.EtlJobController*</include>
</includes>
</configuration>
<executions>
<execution>
<id>prepare-durable-job-coverage</id>
Expand All @@ -118,6 +112,12 @@
<goals>
<goal>report</goal>
</goals>
<configuration>
<includes>
<include>com/xtrmetl/etl/job/*.class</include>
<include>com/xtrmetl/etl/controller/EtlJobController*.class</include>
</includes>
</configuration>
</execution>
<execution>
<id>check-durable-job-coverage</id>
Expand All @@ -126,14 +126,19 @@
<goal>check</goal>
</goals>
<configuration>
<includes>
<include>com/xtrmetl/etl/job/*.class</include>
<include>com/xtrmetl/etl/controller/EtlJobController*.class</include>
</includes>
<rules>
<rule>
<element>CLASS</element>
<includes>
<include>com.xtrmetl.etl.job.*</include>
<include>com.xtrmetl.etl.controller.EtlJobController*</include>
</includes>
<element>BUNDLE</element>
<limits>
<limit>
<counter>CLASS</counter>
<value>TOTALCOUNT</value>
<minimum>1</minimum>
</limit>
<limit>
<counter>INSTRUCTION</counter>
<value>MISSEDCOUNT</value>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> 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<String> executionIncludes(Element plugin, String executionId) {
Element configuration = requireDirectChild(execution(plugin, executionId), "configuration");
Element includes = requireDirectChild(configuration, "includes");
Set<String> 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<Element> directChildren(Element parent, String name) {
Set<Element> 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");
}
}
Loading
Loading