Files
clean-architecture-backend-…/docs/superpowers/specs/2026-09-17-jpa-evidence-gradle-model-decoupling-design.md
T

6.5 KiB

JPA Evidence Gradle Model Decoupling Design

Context

GenerateJpaEvidenceManifestsTask currently performs evidence generation after its producer tasks run. Its semantic contract is useful, but the task action reaches back into the live Gradle model through getProject(), resolves configurations, locates Task instances, reads Test report locations, inspects TaskState, and reads root extra properties.

Gradle 9 deprecates Task.project access at execution time and Gradle 10 will reject it. More importantly, the current task mixes two responsibilities:

  1. Gradle configuration/model discovery.
  2. Pure evidence assembly from producer results.

The refactor must separate those concerns without weakening evidence claims.

Goals

  • Preserve the current readiness-card and evidence-manifest semantics.
  • Remove execution-time Project, Task, and TaskState access from GenerateJpaEvidenceManifestsTask.
  • Preserve JUnit XML as the source of truth for test execution evidence.
  • Preserve successful non-Test task execution as the source of truth for task-claims such as architecture/configuration claims.
  • Represent generator inputs with typed Gradle properties rather than hidden project lookups.
  • Keep producer task names and readiness-card schema unchanged.
  • Remain compatible with --warning-mode=fail on Gradle 9 and prepare the evidence lane for Gradle 10.

Non-goals

  • Do not redesign the readiness-card schema.
  • Do not change evidence grades, prerequisite semantics, content hashing, R1/R2 rules, or output layout.
  • Do not introduce marker files into every producer task.
  • Do not move release orchestration into the persistence-JPA leaf.
  • Do not add new runtime dependencies to application modules.

Architecture

1. Build service owns task completion outcomes

Introduce JpaEvidenceExecutionService, a Gradle shared build service implementing OperationCompletionListener.

The plugin registers it through BuildEventsListenerRegistry.onTaskCompletion(...) so the service receives TaskFinishEvent events without the generator querying TaskState.

The service stores a thread-safe typed outcome for each task path:

Task path
  -> SUCCESS
  -> FAILED
  -> SKIPPED

Only SUCCESS satisfies an evidence task-claim. Failed or skipped producers do not cover the claim.

The service is build-scoped and contains no Project reference.

2. Test evidence remains file-based

JUnit evidence already has a durable output: Gradle's JUnit XML result directory. The plugin resolves every readiness/support Test task during configuration and supplies a typed mapping:

absolute task path -> JUnit XML result directory

The generator reads those directories directly with JUnitEvidenceReader; it never locates a Test object.

Non-Test support tasks continue to participate in the task graph but do not produce JUnit evidence.

3. Configuration-derived values become task inputs

The plugin supplies these inputs before execution:

  • evidence profile
  • CI job
  • artifact location
  • topology
  • PostgreSQL image
  • source revision
  • traceable version
  • resolved PostgreSQL JDBC version
  • resolved Hibernate ORM version
  • resolved Flyway version
  • repository-relative evidence output location used by the candidate default
  • JUnit result-directory mapping

The generator reads only its properties/files plus the execution service.

releaseProvenance is the preferred source for revision/version. The existing extra-property compatibility bridge is no longer read by the generator.

4. Dependency-version discovery stays in plugin configuration

The JPA evidence plugin owns the Gradle Configuration object. It derives the three relevant resolved module versions and writes them into typed task properties before the generator executes.

This keeps dependency-graph access out of the task action. The existing coordinates remain unchanged:

  • org.postgresql:postgresql
  • org.hibernate.orm:hibernate-core
  • org.flywaydb:flyway-core

5. Generator becomes an evidence assembler

The generator task action may use:

  • its declared Gradle properties/files
  • ExecOperations for git/docker commands already owned by the task
  • FileSystemOperations
  • JpaEvidenceExecutionService
  • pure parser/verifier/helper classes

It must not call:

getProject()
Project.findProject(...)
Task.getState()
TaskContainer.findByName(...)
ConfigurationContainer.getByName(...)
ExtraPropertiesExtension.get(...)

6. Evidence semantics

For a readiness card:

  • evidence.scenarios are covered only by selectors found in JUnit XML.
  • evidence.task-claims are covered only when the build service reports the named task completed successfully in the current build.
  • no-skip remains based on JUnit result counts.
  • prerequisite manifest ordering and hashing remain unchanged.
  • candidate/R2 blockers remain unchanged.

The primary foundation card still obtains architecture/configuration coverage from successful execution of its declared producer tasks; the mechanism changes from TaskState lookup to task-finish events, not the meaning.

Error handling

  • A readiness task expected to produce JUnit evidence but missing from the configured result mapping is a hard failure.
  • A configured JUnit result directory that contains no usable result remains subject to the existing JUnit evidence validation.
  • A task claim with no successful completion event is simply uncovered and therefore becomes missing required evidence when that claim is required.
  • Unsupported evidence profile remains a hard failure.
  • Missing immutable image digest/dependency versions retain the existing blocker behavior.

Testing

  1. Unit-test task-event classification in JpaEvidenceExecutionService.
  2. Unit-test pure JUnit result lookup from configured task-path/directory inputs.
  3. TestKit: apply ca.jpa-evidence in a fixture and verify the generator task exposes typed inputs without execution-time project lookup.
  4. Existing JPA evidence verifier tests must remain green.
  5. Run build-tools:check --warning-mode=fail.
  6. Run verifyJpaReadinessRegistry verifyJpaReleaseGateTasks --warning-mode=fail.
  7. Run the affected JPA leaf check.
  8. Run a candidate evidence lane far enough to confirm no Task.project deprecation is emitted; environment-dependent Docker/Testcontainers failure may be reported separately from Gradle-model warnings.

Migration boundary

This change only decouples evidence generation from the live Gradle model. It does not alter the readiness registry, producer tasks, JUnit test suites, manifest schema, release workflow, or evidence verification policy.