Command: static release-registry/task-tag/test-assertion provenance for collection-fetch-pagination
Working directory: /shared/codebase/clean-architecture-backend-template
Executed at: 2026-08-29T08:22:42Z
Source revision: a24ece9cf797f7ea647e33bf846b115208ed1ba5
Observation boundary: Static provenance only. Shows which Gradle tag the registry task selects and what the named collection-fetch test actually asserts; does not execute PostgreSQL.
--- stdout/stderr ---
=== registry gate ===
    {
      "name": "runtime-role-no-ddl",
      "task": ":adapter:outbound:persistence-jpa:jpaPlatformSecurityTest",
      "blocking": true
    },
    {
      "name": "collection-fetch-pagination",
      "task": ":adapter:outbound:persistence-jpa:jpaPlatformQueryPlanTest",
      "blocking": true
    }
  ]
}
=== lane registration ===
// leaf's existing Docker-backed source set; the mapping is recorded in
// docs/jpa/repository-adaptation.md §3.
//
// Every lane fails closed. `failOnNoDiscoveredTests` matters more here than usual: a selected lane
// that discovers nothing reports success, and a contract suite that silently stopped running is
// indistinguishable from one that passes.
Closure<Void> registerJpaPlatformLane = { String taskName, String tag, String description ->
    tasks.register(taskName, Test) {
        group = 'verification'
        it.description = description
        testClassesDirs = sourceSets.postgresqlIntegrationTest.output.classesDirs
        classpath = sourceSets.postgresqlIntegrationTest.runtimeClasspath
        useJUnitPlatform {
            includeTags tag
        }
        failOnNoDiscoveredTests = true
        outputs.upToDateWhen { false }
        jvmArgs('-Duser.timezone=UTC')
        // The Stable matrix selection. An unknown or empty value is an error in
        // PostgreSqlVersion.parseSelection rather than an empty run.
        systemProperty 'jpa.matrix.versions',
                (project.findProperty('jpa.matrix.versions') ?: '16').toString()
    }
}

def jpaPlatformContractTest = registerJpaPlatformLane(
        'jpaPlatformContractTest',
        'jpa-contract',
        'Runs the JPA platform contract suite against real PostgreSQL (design §40).')
def jpaPlatformMigrationTest = registerJpaPlatformLane(
        'jpaPlatformMigrationTest',
        'jpa-migration',
        'Runs the Flyway upgrade snapshot scenarios (design §31).')
def jpaPlatformFailureTest = registerJpaPlatformLane(
        'jpaPlatformFailureTest',
        'jpa-failure',
        'Reproduces deadlock, serialization, and commit-ambiguity failures (design §39).')
def jpaPlatformQueryPlanTest = registerJpaPlatformLane(
        'jpaPlatformQueryPlanTest',
        'jpa-queryplan',
        'Asserts query plan structure and planner estimate error (design §33).')
def jpaPlatformSecurityTest = registerJpaPlatformLane(
        'jpaPlatformSecurityTest',
        'jpa-security',
        'Verifies runtime role privileges and search_path safety (design §36).')

=== tags ===
src/adapter/outbound/persistence-jpa/src/postgresqlIntegrationTest/java/dev/caskeleton/adapter/outbound/persistence/platform/HibernateCollectionFetchPaginationContractTest.java:26:@Tag("jpa-contract")
src/adapter/outbound/persistence-jpa/src/postgresqlIntegrationTest/java/dev/caskeleton/adapter/outbound/persistence/platform/PostgreSqlQueryPlanContractTest.java:27:@Tag("jpa-queryplan")
=== collection-fetch test ===
package dev.caskeleton.adapter.outbound.persistence.platform;

import static org.assertj.core.api.Assertions.assertThat;

import dev.caskeleton.adapter.outbound.persistence.hibernate.HibernateStatisticsSnapshot;
import dev.caskeleton.adapter.outbound.persistence.testkit.fetch.FetchPaginationExpectation;
import dev.caskeleton.adapter.outbound.persistence.testkit.fetch.PagedChild;
import dev.caskeleton.adapter.outbound.persistence.testkit.fetch.PagedParent;
import java.util.List;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

/**
 * The collection-fetch pagination gate (design §26).
 *
 * <p>The failure this exists to catch is in-memory pagination: older providers fetched every
 * matching parent row and applied the page in Java, logging a warning and returning correct results
 * while reading the whole table. Correct output, unbounded work, and nothing failing anywhere.
 *
 * <p>The assertion is therefore on the <em>generated SQL</em>, not on the returned page size. The
 * page is identical either way; only the SQL says where the limit was applied.
 */
@Tag("jpa-contract")
class HibernateCollectionFetchPaginationContractTest {

  private static final int PARENTS = 50;
  private static final int CHILDREN_PER_PARENT = 4;
  private static final int PAGE_SIZE = 20;

  private static JpaPlatformContractSupport support;
  private static JpaPlatformEntityManagerSupport jpa;

  @BeforeAll
  static void startServerAndSeed() {
    support = JpaPlatformContractSupport.start();
    jpa = JpaPlatformEntityManagerSupport.open(support, PagedParent.class, PagedChild.class);
    jpa.inTransactionDo(
        entityManager -> {
          for (int parent = 0; parent < PARENTS; parent++) {
            var paged = new PagedParent("parent-" + parent);
            for (int child = 0; child < CHILDREN_PER_PARENT; child++) {
              paged.addChild(new PagedChild("child-" + parent + '-' + child));
            }
            entityManager.persist(paged);
          }
        });
  }

  @AfterAll
  static void stopServer() {
    if (jpa != null) {
      jpa.close();
    }
    if (support != null) {
      support.close();
    }
  }

  @Test
  @DisplayName("a paged collection fetch bounds the parent selection in SQL")
  void oneCollectionPageIsBoundedInSql() {
    var expected = FetchPaginationExpectation.hibernate74PostgreSql(PAGE_SIZE);

    List<PagedParent> page =
        jpa.inTransaction(
            entityManager ->
                entityManager
                    .createQuery(
                        "select distinct p from PagedParent p left join fetch p.children"
                            + " order by p.id",
                        PagedParent.class)
                    .setMaxResults(PAGE_SIZE)
                    .getResultList());

    assertThat(page).hasSizeLessThanOrEqualTo(expected.maxReturnedParents());
    assertThat(expected.requiresDatabaseLimit()).isTrue();
  }

  @Test
  @DisplayName("the fetched page issues one statement, not one per parent")
  void pagedFetchIssuesOneStatement() {
    HibernateStatisticsSnapshot before = jpa.statistics().snapshot();

    jpa.inTransactionDo(
        entityManager -> {
          List<PagedParent> page =
              entityManager
                  .createQuery(
                      "select distinct p from PagedParent p left join fetch p.children"
                          + " order by p.id",
                      PagedParent.class)
                  .setMaxResults(PAGE_SIZE)
                  .getResultList();
          page.forEach(parent -> assertThat(parent.children()).isNotNull());
        });

    HibernateStatisticsSnapshot delta = jpa.statistics().snapshot().minus(before);
    assertThat(delta.preparedStatements())
        .as("a join fetch must not degrade into one statement per parent")
        .isLessThanOrEqualTo(2L);
  }

  @Test
  @DisplayName("without a fetch join the same access is an N+1")
  void withoutFetchJoinTheAccessIsAnNplusOne() {
    HibernateStatisticsSnapshot before = jpa.statistics().snapshot();

    jpa.inTransactionDo(
        entityManager -> {
          List<PagedParent> page =
              entityManager
                  .createQuery("select p from PagedParent p order by p.id", PagedParent.class)
=== query-plan producer test ===
package dev.caskeleton.adapter.outbound.persistence.platform;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import dev.caskeleton.adapter.outbound.persistence.testkit.queryplan.NormalizedPlan;
import dev.caskeleton.adapter.outbound.persistence.testkit.queryplan.PostgreSqlExplainRunner;
import dev.caskeleton.adapter.outbound.persistence.testkit.queryplan.QueryPlanAssertions;
import dev.caskeleton.adapter.outbound.persistence.testkit.queryplan.QueryPlanExpectation;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

/**
 * Captures a real plan and asserts on its structure (design §33).
 *
 * <p>Structure, not cost: costs and timings differ on every execution and every machine, so a
 * snapshot including them fails for reasons that have nothing to do with the query. What is stable
 * is which node types appear, how far the planner's estimate was from reality, and whether a sort
 * spilled to disk.
 */
@Tag("jpa-queryplan")
class PostgreSqlQueryPlanContractTest {

  private static JpaPlatformContractSupport support;
  private static PostgreSqlExplainRunner runner;

  private final QueryPlanAssertions assertions = new QueryPlanAssertions();

  @BeforeAll
  static void startServer() throws SQLException {
    support = JpaPlatformContractSupport.start();
    runner = new PostgreSqlExplainRunner(support.dataSource());
    try (Connection connection = support.connection();
        Statement statement = connection.createStatement()) {
      statement.execute("create table plan_row (id bigint primary key, bucket int not null)");
      statement.execute(
          "insert into plan_row(id, bucket) select generate_series(1, 5000),"
              + " (random() * 10)::int");
      statement.execute("create index ix_plan_row_bucket on plan_row(bucket)");
      statement.execute("analyze plan_row");
    }
  }

  @AfterAll
  static void stopServer() {
    if (support != null) {
      support.close();
    }
  }

  @Test
  @DisplayName("an indexed lookup produces a plan whose estimate is close to reality")
  void indexedLookupHasAccurateEstimate() {
    NormalizedPlan plan = runner.explain("select id from plan_row where id = ?", 42L);

    assertions.assertMatches(
        plan, QueryPlanExpectation.estimateOnly(10.0d).withDiskSortForbidden());
    assertThat(plan.distinctNodeTypes()).isNotEmpty();
  }

  @Test
  @DisplayName("a forbidden node type is reported with the whole plan")
  void forbiddenNodeTypeIsReported() {
    NormalizedPlan plan = runner.explain("select id from plan_row where bucket = ?", 3);

    assertThatThrownBy(
            () ->
                assertions.assertMatches(
                    plan,
                    QueryPlanExpectation.estimateOnly(10.0d)
                        .forbidding(
                            "Bitmap Heap Scan", "Index Scan", "Seq Scan", "Index Only Scan")))
        .isInstanceOf(AssertionError.class)
        .hasMessageContaining("plan ");
  }

  @Test
  @DisplayName("EXPLAIN ANALYZE refuses anything that is not a SELECT")
  void refusesNonSelect() {
    assertThatThrownBy(() -> runner.explain("update plan_row set bucket = 1"))
        .isInstanceOf(IllegalArgumentException.class)
        .hasMessageContaining("only SELECT");
  }
}

Exit code: 0
