feat: add JPA production capability

This commit is contained in:
donghyeon-ka
2026-07-31 23:48:51 +09:00
parent b3add0162d
commit 7eb6af5d5f
141 changed files with 13094 additions and 319 deletions
+930
View File
@@ -0,0 +1,930 @@
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import groovy.xml.XmlSlurper
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import java.time.Instant
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.tasks.testing.Test
/*
* JPA readiness evidence producer.
*
* The registry owns the card/task/scenario mapping. This script only accepts evidence emitted by
* tasks in that registry, reads their JUnit XML, and writes one content-addressed manifest per
* active card. The candidate verifier deliberately permits incomplete R2 dimensions while the
* canonical primary-foundation task requires a clean CI R2 profile and a complete prerequisite
* manifest DAG.
*/
File jpaEvidenceRegistryFile = rootProject.file('config/jpa/readiness-cards.yaml')
def jpaEvidenceOutputDirectory = layout.buildDirectory.dir('jpa-evidence/manifests')
String jpaEvidenceImage = project.ext.jpaPostgreSqlEvidenceImage as String
Closure<Object> canonicalizeJpaEvidence
canonicalizeJpaEvidence = { Object value ->
if (value instanceof Map) {
Map<String, Object> sorted = new TreeMap<>()
(value as Map).each { Object key, Object child ->
sorted[key as String] = canonicalizeJpaEvidence(child)
}
return sorted
}
if (value instanceof List) {
return (value as List).collect { Object child -> canonicalizeJpaEvidence(child) }
}
value
}
Closure<String> canonicalJpaEvidenceJson = { Object value ->
JsonOutput.toJson(canonicalizeJpaEvidence(value))
}
Closure<String> sha256JpaEvidence = { String value ->
MessageDigest digest = MessageDigest.getInstance('SHA-256')
digest.digest(value.getBytes(StandardCharsets.UTF_8)).encodeHex().toString()
}
Closure<Task> jpaEvidenceTaskAtPath = { String absoluteTaskPath ->
int separator = absoluteTaskPath.lastIndexOf(':')
if (separator < 0 || separator == absoluteTaskPath.length() - 1) {
throw new GradleException("Invalid absolute Gradle task path '${absoluteTaskPath}'")
}
String projectPath = separator == 0 ? ':' : absoluteTaskPath.substring(0, separator)
String taskName = absoluteTaskPath.substring(separator + 1)
Project owner = rootProject.findProject(projectPath)
if (owner == null) {
throw new GradleException("Unknown project for JPA evidence task '${absoluteTaskPath}'")
}
Task task = owner.tasks.findByName(taskName)
if (task == null) {
throw new GradleException("Missing JPA evidence task '${absoluteTaskPath}'")
}
task
}
Closure<String> runJpaEvidenceCommand = { List<String> command ->
providers.exec {
commandLine command
ignoreExitValue = true
}.standardOutput.asText.get().trim()
}
Closure<Map<String, Object>> readJpaJUnitResult = { Test testTask ->
File resultDirectory = testTask.reports.junitXml.outputLocation.get().asFile
List<File> resultFiles = resultDirectory.isDirectory()
? (resultDirectory.listFiles() ?: [] as File[])
.findAll { File result -> result.name.startsWith('TEST-') && result.name.endsWith('.xml') }
.toSorted { File left, File right -> left.name <=> right.name }
: []
if (resultFiles.isEmpty()) {
throw new GradleException(
"${testTask.path}: JUnit XML evidence is missing from ${resultDirectory}")
}
int executed = 0
int skipped = 0
int failures = 0
int errors = 0
Set<String> selectors = new TreeSet<>()
resultFiles.each { File resultFile ->
def suite = new XmlSlurper(false, false).parse(resultFile)
executed += (suite.@tests.text() ?: '0') as int
skipped += (suite.@skipped.text() ?: '0') as int
failures += (suite.@failures.text() ?: '0') as int
errors += (suite.@errors.text() ?: '0') as int
suite.testcase.each { Object rawCase ->
String className = rawCase.@classname.text()
String methodName = rawCase.@name.text().replaceFirst(/\([^)]*\)$/, '')
selectors << "${className}#${methodName}".toString()
}
}
[
tasks: [testTask.path],
resultDirectories: [rootProject.relativePath(resultDirectory)],
executedTestCount: executed,
skippedOrAbortedCount: skipped,
failureCount: failures,
errorCount: errors,
noSkipResult: executed > 0 && skipped == 0 && failures == 0 && errors == 0,
executedSelectors: selectors.toList()
] as Map<String, Object>
}
Closure<List<String>> requiredJpaEvidence = { Map<String, Object> card ->
List<String> required = (card['required-evidence'] as List)
.collect { Object item -> item as String }
if (card.migration instanceof Map) {
Object rawLifecycle = (card.migration as Map)['lifecycle-evidence']
if (rawLifecycle instanceof List) {
(rawLifecycle as List).each { Object lifecycle ->
required << "migration-lifecycle:${lifecycle as String}".toString()
}
}
}
required.toSet().toSorted()
}
Closure<Map<String, String>> resolvedJpaEvidenceVersions = {
Map<String, String> versions = [:]
configurations.postgresqlIntegrationTestRuntimeClasspath
.incoming
.resolutionResult
.allComponents
.each { component ->
if (component.id instanceof ModuleComponentIdentifier) {
ModuleComponentIdentifier id = component.id as ModuleComponentIdentifier
versions["${id.group}:${id.module}".toString()] = id.version
}
}
[
pgjdbc: versions['org.postgresql:postgresql'] ?: '',
hibernate: versions['org.hibernate.orm:hibernate-core'] ?: '',
flyway: versions['org.flywaydb:flyway-core'] ?: ''
] as Map<String, String>
}
Set<String> expectedJpaEvidenceManifestKeys = [
'schemaVersion',
'cardId',
'cardVersion',
'declaredState',
'attainedReadiness',
'evidenceGrade',
'profile',
'prerequisites',
'source',
'producer',
'testResult',
'requiredEvidence',
'coveredEvidence',
'missingEvidence',
'readinessBlockers',
'postgresql',
'dependencies',
'generatedAt',
'date',
'topology',
'artifactLocation',
'migration',
'dispatchModes'
] as Set
Closure<List<String>> validateJpaEvidenceManifest = {
Map<String, Object> card,
Map<String, Object> manifest ->
List<String> violations = []
String cardId = manifest.cardId as String
Set<String> actualKeys = manifest.keySet().collect { it as String }.toSet()
if (actualKeys != expectedJpaEvidenceManifestKeys) {
violations << "${cardId}: manifest keys must be exactly ${expectedJpaEvidenceManifestKeys}"
}
if (manifest.schemaVersion != 1) {
violations << "${cardId}: schemaVersion must be 1"
}
if (cardId == null || cardId.isBlank()) {
violations << 'manifest cardId must be non-blank'
}
if (!(manifest.declaredState in ['selected', 'implemented-candidate'])) {
violations << "${cardId}: invalid declaredState '${manifest.declaredState}'"
}
if (!(manifest.attainedReadiness in ['R1', 'R2'])) {
violations << "${cardId}: invalid attainedReadiness '${manifest.attainedReadiness}'"
}
if (!(manifest.evidenceGrade in ['E1', 'E2', 'E3'])) {
violations << "${cardId}: invalid evidenceGrade '${manifest.evidenceGrade}'"
}
if (!(manifest.profile in ['candidate', 'r2'])) {
violations << "${cardId}: invalid profile '${manifest.profile}'"
}
Map<String, Object> source = manifest.source instanceof Map
? manifest.source as Map<String, Object>
: [:]
if (source.keySet().collect { it as String }.toSet() !=
['revision', 'worktreeDirty', 'worktreeStatusDigest'] as Set) {
violations << "${cardId}: invalid source metadata keys"
}
if (!((source.revision as String) ==~ /[0-9a-f]{7,40}/)) {
violations << "${cardId}: invalid source revision '${source.revision}'"
}
if (!(source.worktreeDirty instanceof Boolean)) {
violations << "${cardId}: worktreeDirty must be boolean"
}
if (!((source.worktreeStatusDigest as String) ==~ /[0-9a-f]{64}/)) {
violations << "${cardId}: invalid worktree status digest"
}
Map<String, Object> producer = manifest.producer instanceof Map
? manifest.producer as Map<String, Object>
: [:]
if (producer.keySet().collect { it as String }.toSet() !=
['gradleTask', 'ciJob'] as Set) {
violations << "${cardId}: invalid producer metadata keys"
}
if (!((producer.gradleTask as String)?.startsWith(':'))) {
violations << "${cardId}: producer Gradle task must be absolute"
}
if ((producer.ciJob as String)?.isBlank()) {
violations << "${cardId}: producer CI job must be non-blank"
}
Map<String, Object> testResult = manifest.testResult instanceof Map
? manifest.testResult as Map<String, Object>
: [:]
Set<String> expectedTestKeys = [
'tasks',
'resultDirectories',
'executedTestCount',
'skippedOrAbortedCount',
'failureCount',
'errorCount',
'noSkipResult',
'executedSelectors'
] as Set
if (testResult.keySet().collect { it as String }.toSet() != expectedTestKeys) {
violations << "${cardId}: invalid testResult keys"
}
if (!((testResult.executedTestCount ?: 0) instanceof Number) ||
(testResult.executedTestCount as int) <= 0) {
violations << "${cardId}: executed test count must be positive"
}
['skippedOrAbortedCount', 'failureCount', 'errorCount'].each { String countKey ->
if (!((testResult[countKey] ?: 0) instanceof Number) ||
(testResult[countKey] as int) != 0) {
violations << "${cardId}: ${countKey} must be zero"
}
}
if (testResult.noSkipResult != true) {
violations << "${cardId}: no-skip sentinel must be true"
}
List<String> required = manifest.requiredEvidence instanceof List
? (manifest.requiredEvidence as List).collect { it as String }.toSorted()
: []
List<String> covered = manifest.coveredEvidence instanceof List
? (manifest.coveredEvidence as List).collect { it as String }.toSorted()
: []
List<String> missing = manifest.missingEvidence instanceof List
? (manifest.missingEvidence as List).collect { it as String }.toSorted()
: []
if (required != requiredJpaEvidence(card)) {
violations << "${cardId}: required evidence drifted from registry"
}
if (missing != (required - covered).toSorted()) {
violations << "${cardId}: missing evidence is not required minus covered"
}
Map<String, Object> postgresql = manifest.postgresql instanceof Map
? manifest.postgresql as Map<String, Object>
: [:]
if (postgresql.keySet().collect { it as String }.toSet() !=
['image', 'imageDigest', 'managedEngineVersion'] as Set) {
violations << "${cardId}: invalid PostgreSQL metadata keys"
}
if (!((postgresql.imageDigest as String) ==~ /.+@sha256:[0-9a-f]{64}/)) {
violations << "${cardId}: PostgreSQL image digest must be immutable"
}
Map<String, Object> dependencies = manifest.dependencies instanceof Map
? manifest.dependencies as Map<String, Object>
: [:]
if (dependencies.keySet().collect { it as String }.toSet() !=
['pgjdbc', 'hibernate', 'flyway'] as Set ||
dependencies.values().any { Object version -> (version as String)?.isBlank() }) {
violations << "${cardId}: pgjdbc/Hibernate/Flyway versions must be present"
}
try {
Instant.parse(manifest.generatedAt as String)
} catch (RuntimeException ignored) {
violations << "${cardId}: generatedAt must be an ISO-8601 instant"
}
if (!((manifest.date as String) ==~ /\d{4}-\d{2}-\d{2}/)) {
violations << "${cardId}: date must be ISO-8601"
}
if ((manifest.topology as String)?.isBlank()) {
violations << "${cardId}: topology must be non-blank"
}
if ((manifest.artifactLocation as String)?.isBlank()) {
violations << "${cardId}: artifactLocation must be non-blank"
}
List<Object> prerequisites = manifest.prerequisites instanceof List
? manifest.prerequisites as List<Object>
: []
prerequisites.eachWithIndex { Object rawPrerequisite, int index ->
Map<String, Object> prerequisite = rawPrerequisite instanceof Map
? rawPrerequisite as Map<String, Object>
: [:]
if (prerequisite.keySet().collect { it as String }.toSet() !=
['cardId', 'cardVersion', 'manifestId', 'attainedReadiness'] as Set) {
violations << "${cardId}: prerequisite ${index} has invalid keys"
}
if (!((prerequisite.manifestId as String) ==~ /sha256:[0-9a-f]{64}/)) {
violations << "${cardId}: prerequisite ${index} has invalid manifest ID"
}
}
if (card.migration instanceof Map) {
Map<String, Object> migration = manifest.migration instanceof Map
? manifest.migration as Map<String, Object>
: [:]
Set<String> expectedMigrationKeys = [
'location',
'historyTable',
'requiredCoreEpoch',
'featureRevision',
'streamLifecycleEvidenceIds'
] as Set
if (migration.keySet().collect { it as String }.toSet() != expectedMigrationKeys) {
violations << "${cardId}: schema-bearing manifest has invalid migration metadata"
}
} else if (manifest.migration != null) {
violations << "${cardId}: non-schema card must not contain migration metadata"
}
if (card['dispatch-modes'] instanceof List) {
if (manifest.dispatchModes != card['dispatch-modes']) {
violations << "${cardId}: dispatch modes drifted from registry"
}
} else if (manifest.dispatchModes != []) {
violations << "${cardId}: non-outbox card must have empty dispatch modes"
}
if (manifest.attainedReadiness == 'R2') {
if (manifest.profile != 'r2') {
violations << "${cardId}: R2 requires the r2 profile"
}
if (source.worktreeDirty != false) {
violations << "${cardId}: R2 requires a clean worktree"
}
if (!missing.isEmpty()) {
violations << "${cardId}: R2 has missing evidence ${missing}"
}
if (producer.ciJob == 'local-unpublished') {
violations << "${cardId}: R2 requires a real CI job identity"
}
if (!((manifest.artifactLocation as String) ==~
/(?i)(https|s3|gs):\/\/\S+/)) {
violations << "${cardId}: R2 requires an externally retained artifact location"
}
}
violations
}
Closure<Map<String, Object>> loadJpaEvidenceRegistry = {
new JsonSlurper().parse(jpaEvidenceRegistryFile) as Map<String, Object>
}
Closure<Map<String, Object>> verifyJpaEvidenceDirectory = {
File outputDirectory,
Map<String, Object> registry ->
List<String> violations = []
Map<String, Object> manifests = [:]
Map<String, String> manifestIds = [:]
Map<String, Object> activeCards = (registry.cards as Map<String, Object>).findAll {
String ignored, Object rawCard ->
((rawCard as Map).state as String) != 'not-implemented'
}
activeCards.each { String cardId, Object rawCard ->
File cardDirectory = new File(outputDirectory, cardId)
List<File> files = cardDirectory.isDirectory()
? (cardDirectory.listFiles() ?: [] as File[])
.findAll { File file -> file.name.endsWith('.json') }
: []
if (files.size() != 1) {
violations << "${cardId}: expected exactly one content-addressed manifest; got ${files.size()}"
return
}
File manifestFile = files[0]
String fileHash = manifestFile.name.substring(0, manifestFile.name.length() - '.json'.length())
Map<String, Object> manifest =
new JsonSlurper().parse(manifestFile) as Map<String, Object>
String contentHash = sha256JpaEvidence(canonicalJpaEvidenceJson(manifest))
if (fileHash != contentHash) {
violations << "${cardId}: filename hash ${fileHash} does not match content ${contentHash}"
}
if ((manifest.cardId as String) != cardId) {
violations << "${cardId}: manifest cardId is '${manifest.cardId}'"
}
violations.addAll(validateJpaEvidenceManifest(
rawCard as Map<String, Object>,
manifest))
manifests[cardId] = manifest
manifestIds[cardId] = "sha256:${contentHash}".toString()
}
manifests.each { String cardId, Object rawManifest ->
Map<String, Object> manifest = rawManifest as Map<String, Object>
(manifest.prerequisites as List).each { Object rawPrerequisite ->
Map<String, Object> prerequisite = rawPrerequisite as Map<String, Object>
String prerequisiteId = prerequisite.cardId as String
if (manifestIds[prerequisiteId] != prerequisite.manifestId) {
violations << "${cardId}: prerequisite ${prerequisiteId} manifest ID does not match"
}
}
}
[violations: violations, manifests: manifests, manifestIds: manifestIds]
}
def verifyJpaEvidenceHarnessContract = tasks.register('verifyJpaEvidenceHarnessContract') {
group = 'verification'
description = 'Mutation-tests JPA evidence schema, no-skip, content hash, and R2 provenance checks.'
doLast {
Map<String, Object> card = [
state: 'selected',
'required-evidence': ['real-postgresql', 'no-skip']
]
Map<String, Object> valid = [
schemaVersion: 1,
cardId: 'jpa-contract-fixture',
cardVersion: '1',
declaredState: 'selected',
attainedReadiness: 'R1',
evidenceGrade: 'E2',
profile: 'candidate',
prerequisites: [],
source: [
revision: 'b3add0162df8',
worktreeDirty: true,
worktreeStatusDigest: '0' * 64
],
producer: [
gradleTask: ':adapter:outbound:persistence-jpa:contractFixture',
ciJob: 'local-unpublished'
],
testResult: [
tasks: [':adapter:outbound:persistence-jpa:contractFixture'],
resultDirectories: ['build/test-results/contractFixture'],
executedTestCount: 1,
skippedOrAbortedCount: 0,
failureCount: 0,
errorCount: 0,
noSkipResult: true,
executedSelectors: ['dev.caskeleton.ContractFixture#passes']
],
requiredEvidence: ['no-skip', 'real-postgresql'],
coveredEvidence: ['no-skip', 'real-postgresql'],
missingEvidence: [],
readinessBlockers: ['candidate-profile-is-not-release-evidence'],
postgresql: [
image: 'postgres:16-alpine',
imageDigest: "postgres@sha256:${'1' * 64}".toString(),
managedEngineVersion: '16'
],
dependencies: [
pgjdbc: '42.7.8',
hibernate: '7.1.8.Final',
flyway: '11.14.1'
],
generatedAt: '2026-07-28T00:00:00Z',
date: '2026-07-28',
topology: 'single-postgresql-testcontainer',
artifactLocation: 'build/jpa-evidence/manifests',
migration: null,
dispatchModes: []
]
List<String> baseline = validateJpaEvidenceManifest(card, valid)
if (!baseline.isEmpty()) {
throw new GradleException(
"verifyJpaEvidenceHarnessContract: valid fixture failed ${baseline}")
}
Map<String, Object> skipped =
new JsonSlurper().parseText(JsonOutput.toJson(valid)) as Map<String, Object>
(skipped.testResult as Map).skippedOrAbortedCount = 1
(skipped.testResult as Map).noSkipResult = false
List<String> skippedViolations = validateJpaEvidenceManifest(card, skipped)
if (!skippedViolations.any { String violation -> violation.contains('must be zero') } ||
!skippedViolations.any { String violation -> violation.contains('sentinel must be true') }) {
throw new GradleException(
"verifyJpaEvidenceHarnessContract: skip mutation escaped ${skippedViolations}")
}
Map<String, Object> dirtyR2 =
new JsonSlurper().parseText(JsonOutput.toJson(valid)) as Map<String, Object>
dirtyR2.attainedReadiness = 'R2'
dirtyR2.profile = 'r2'
List<String> dirtyViolations = validateJpaEvidenceManifest(card, dirtyR2)
if (!dirtyViolations.any { String violation -> violation.contains('clean worktree') } ||
!dirtyViolations.any { String violation -> violation.contains('real CI job') }) {
throw new GradleException(
"verifyJpaEvidenceHarnessContract: R2 provenance mutation escaped ${dirtyViolations}")
}
String validHash = sha256JpaEvidence(canonicalJpaEvidenceJson(valid))
Map<String, Object> mutated =
new JsonSlurper().parseText(JsonOutput.toJson(valid)) as Map<String, Object>
mutated.topology = 'mutated-topology'
String mutatedHash = sha256JpaEvidence(canonicalJpaEvidenceJson(mutated))
if (validHash == mutatedHash) {
throw new GradleException(
'verifyJpaEvidenceHarnessContract: content mutation did not change manifest ID')
}
logger.lifecycle(
'verifyJpaEvidenceHarnessContract: OK — skip, dirty/local R2, and content mutation fail closed.')
}
}
def generateJpaEvidenceManifests = tasks.register('generateJpaEvidenceManifests') {
group = 'verification'
description = 'Runs active JPA card producers and writes content-addressed candidate/R2 manifests.'
dependsOn verifyJpaEvidenceHarnessContract
dependsOn rootProject.tasks.named('verifyJpaReadinessRegistry')
Map<String, Object> configuredRegistry = loadJpaEvidenceRegistry()
Map<String, Object> configuredActiveCards =
(configuredRegistry.cards as Map<String, Object>).findAll {
String ignored, Object rawCard ->
((rawCard as Map).state as String) != 'not-implemented'
}
configuredActiveCards.each { String cardId, Object rawCard ->
Map<String, Object> card = rawCard as Map<String, Object>
if (cardId != 'jpa-primary-foundation') {
dependsOn jpaEvidenceTaskAtPath(card['readiness-task'] as String)
}
((card['support-tasks'] ?: []) as List).each { Object taskPath ->
dependsOn jpaEvidenceTaskAtPath(taskPath as String)
}
}
Map<String, Object> primaryCard =
configuredActiveCards['jpa-primary-foundation'] as Map<String, Object>
(primaryCard['support-tasks'] as List).each { Object taskPath ->
dependsOn jpaEvidenceTaskAtPath(taskPath as String)
}
outputs.dir(jpaEvidenceOutputDirectory)
outputs.upToDateWhen { false }
doLast {
Map<String, Object> registry = loadJpaEvidenceRegistry()
Map<String, Object> cards = registry.cards as Map<String, Object>
Map<String, Object> activeCards = cards.findAll {
String ignored, Object rawCard ->
((rawCard as Map).state as String) != 'not-implemented'
}
String profile = providers.gradleProperty('jpaEvidenceProfile')
.orElse(providers.environmentVariable('JPA_EVIDENCE_PROFILE'))
.getOrElse('candidate')
if (!(profile in ['candidate', 'r2'])) {
throw new GradleException(
"jpaEvidenceProfile must be candidate or r2; got '${profile}'")
}
String ciJob = providers.environmentVariable('JPA_EVIDENCE_CI_JOB')
.getOrElse(profile == 'candidate' ? 'local-unpublished' : '')
String configuredArtifactLocation =
providers.environmentVariable('JPA_EVIDENCE_ARTIFACT_LOCATION')
.getOrElse(profile == 'candidate'
? rootProject.relativePath(jpaEvidenceOutputDirectory.get().asFile)
: '')
String topology = providers.environmentVariable('JPA_EVIDENCE_TOPOLOGY')
.getOrElse('single-postgresql-testcontainer')
String worktreeStatus = runJpaEvidenceCommand(
['git', 'status', '--porcelain=v1', '--untracked-files=all'])
boolean worktreeDirty = !worktreeStatus.isBlank()
String worktreeStatusDigest = sha256JpaEvidence(worktreeStatus)
String imageDigest = runJpaEvidenceCommand([
'docker',
'image',
'inspect',
'--format={{index .RepoDigests 0}}',
jpaEvidenceImage
])
Map<String, String> dependencyVersions = resolvedJpaEvidenceVersions()
List<String> productionMetadataBlockers = []
if (profile == 'r2') {
if (worktreeDirty) {
productionMetadataBlockers << 'worktree-is-dirty'
}
if (ciJob.isBlank()) {
productionMetadataBlockers << 'missing-JPA_EVIDENCE_CI_JOB'
}
if (configuredArtifactLocation.isBlank()) {
productionMetadataBlockers << 'missing-JPA_EVIDENCE_ARTIFACT_LOCATION'
} else if (!(configuredArtifactLocation ==~ /(?i)(https|s3|gs):\/\/\S+/)) {
productionMetadataBlockers << 'artifact-location-is-not-externally-retained'
}
}
if (!(imageDigest ==~ /.+@sha256:[0-9a-f]{64}/)) {
productionMetadataBlockers << 'missing-immutable-postgresql-image-digest'
}
dependencyVersions.each { String component, String version ->
if (version.isBlank()) {
productionMetadataBlockers << "missing-${component}-version".toString()
}
}
File outputDirectory = jpaEvidenceOutputDirectory.get().asFile
delete(outputDirectory)
outputDirectory.mkdirs()
Map<String, Object> manifests = [:]
Map<String, String> manifestIds = [:]
activeCards.each { String cardId, Object rawCard ->
Map<String, Object> card = rawCard as Map<String, Object>
List<Map<String, Object>> testResults = []
if (cardId == 'jpa-primary-foundation') {
(card.prerequisites as List).each { Object prerequisite ->
Map<String, Object> prerequisiteManifest =
manifests[prerequisite as String] as Map<String, Object>
if (prerequisiteManifest != null) {
testResults << (prerequisiteManifest.testResult as Map<String, Object>)
}
}
} else {
Task readinessTask = jpaEvidenceTaskAtPath(card['readiness-task'] as String)
if (!(readinessTask instanceof Test)) {
throw new GradleException(
"${cardId}: readiness task ${readinessTask.path} must be a Test task")
}
testResults << readJpaJUnitResult(readinessTask as Test)
((card['support-tasks'] ?: []) as List).each { Object taskPath ->
Task supportTask = jpaEvidenceTaskAtPath(taskPath as String)
if (supportTask instanceof Test) {
testResults << readJpaJUnitResult(supportTask as Test)
}
}
}
Set<String> executedSelectors = testResults
.collectMany { Map<String, Object> result ->
result.executedSelectors as List<String>
}
.toSet()
Set<String> covered = new TreeSet<>()
List<Map<String, Object>> scenarios =
((card.evidence as Map).scenarios as List<Map<String, Object>>)
scenarios.each { Map<String, Object> scenario ->
if (executedSelectors.contains(scenario.selector as String)) {
covered.addAll((scenario.covers as List).collect { it as String })
}
}
List<Map<String, Object>> taskClaims =
((card.evidence as Map)['task-claims'] as List<Map<String, Object>>)
taskClaims.each { Map<String, Object> taskClaim ->
Task evidenceTask = jpaEvidenceTaskAtPath(taskClaim.task as String)
if (evidenceTask.state.executed &&
evidenceTask.state.failure == null &&
!evidenceTask.state.skipped) {
covered.addAll((taskClaim.covers as List).collect { it as String })
}
}
int executedTestCount = testResults.sum {
Map<String, Object> result -> result.executedTestCount as int
} as int
int skippedOrAbortedCount = testResults.sum {
Map<String, Object> result -> result.skippedOrAbortedCount as int
} as int
int failureCount = testResults.sum {
Map<String, Object> result -> result.failureCount as int
} as int
int errorCount = testResults.sum {
Map<String, Object> result -> result.errorCount as int
} as int
boolean noSkipResult = executedTestCount > 0 &&
skippedOrAbortedCount == 0 &&
failureCount == 0 &&
errorCount == 0
if (noSkipResult) {
covered << 'no-skip'
}
if (cardId == 'jpa-primary-foundation' &&
(card.prerequisites as List).every {
Object prerequisite -> manifestIds.containsKey(prerequisite as String)
}) {
covered << 'base-card-manifests'
}
List<String> required = requiredJpaEvidence(card)
List<String> coveredList = covered.findAll {
String claim -> required.contains(claim)
}.toList().sort()
List<String> missing = (required - coveredList).toSorted()
List<Map<String, Object>> prerequisites = (card.prerequisites as List).collect {
Object rawPrerequisite ->
String prerequisiteId = rawPrerequisite as String
Map<String, Object> prerequisiteManifest =
manifests[prerequisiteId] as Map<String, Object>
if (prerequisiteManifest == null || manifestIds[prerequisiteId] == null) {
throw new GradleException(
"${cardId}: prerequisite manifest '${prerequisiteId}' was not produced first")
}
[
cardId: prerequisiteId,
cardVersion: prerequisiteManifest.cardVersion,
manifestId: manifestIds[prerequisiteId],
attainedReadiness: prerequisiteManifest.attainedReadiness
] as Map<String, Object>
}
List<String> readinessBlockers = []
if (profile == 'candidate') {
readinessBlockers << 'candidate-profile-is-not-release-evidence'
}
readinessBlockers.addAll(productionMetadataBlockers)
missing.each { String requirement ->
readinessBlockers << "missing-evidence:${requirement}".toString()
}
prerequisites.findAll {
Map<String, Object> prerequisite ->
prerequisite.attainedReadiness != 'R2'
}.each { Map<String, Object> prerequisite ->
readinessBlockers <<
"prerequisite-not-R2:${prerequisite.cardId}".toString()
}
boolean attainedR2 = profile == 'r2' &&
readinessBlockers.isEmpty() &&
missing.isEmpty()
String generatedAt = Instant.now().toString()
String cardVersion = card.migration instanceof Map
? ((card.migration as Map)['feature-revision'] as Integer).toString()
: rootProject.ext.traceableVersion as String
String evidenceGrade = cardId == 'jpa-primary-foundation'
? 'E1'
: (covered.any { String claim ->
claim in [
'concurrency',
'fault',
'publish-fault',
'migration',
'query-plan',
'optimistic-conflict'
]
} ? 'E3' : 'E2')
Map<String, Object> migration = card.migration instanceof Map
? [
location: (card.migration as Map).location,
historyTable: (card.migration as Map)['history-table'],
requiredCoreEpoch: (card.migration as Map)['required-core-epoch'],
featureRevision: (card.migration as Map)['feature-revision'],
streamLifecycleEvidenceIds:
(card.migration as Map)['lifecycle-evidence']
] as Map<String, Object>
: null
Map<String, Object> manifest = [
schemaVersion: 1,
cardId: cardId,
cardVersion: cardVersion,
declaredState: card.state,
attainedReadiness: attainedR2 ? 'R2' : 'R1',
evidenceGrade: evidenceGrade,
profile: profile,
prerequisites: prerequisites,
source: [
revision: rootProject.ext.sourceRevision as String,
worktreeDirty: worktreeDirty,
worktreeStatusDigest: worktreeStatusDigest
],
producer: [
gradleTask: card['readiness-task'],
ciJob: ciJob
],
testResult: [
tasks: testResults.collectMany {
Map<String, Object> result -> result.tasks as List<String>
}.toSet().toList().sort(),
resultDirectories: testResults.collectMany {
Map<String, Object> result -> result.resultDirectories as List<String>
}.toSet().toList().sort(),
executedTestCount: executedTestCount,
skippedOrAbortedCount: skippedOrAbortedCount,
failureCount: failureCount,
errorCount: errorCount,
noSkipResult: noSkipResult,
executedSelectors: executedSelectors.toSorted()
],
requiredEvidence: required,
coveredEvidence: coveredList,
missingEvidence: missing,
readinessBlockers: readinessBlockers.toSet().toList().sort(),
postgresql: [
image: jpaEvidenceImage,
imageDigest: imageDigest,
managedEngineVersion: '16'
],
dependencies: dependencyVersions,
generatedAt: generatedAt,
date: generatedAt.substring(0, 10),
topology: topology,
artifactLocation: configuredArtifactLocation,
migration: migration,
dispatchModes: card['dispatch-modes'] instanceof List
? card['dispatch-modes']
: []
] as Map<String, Object>
String contentHash = sha256JpaEvidence(canonicalJpaEvidenceJson(manifest))
File cardDirectory = new File(outputDirectory, cardId)
cardDirectory.mkdirs()
File manifestFile = new File(cardDirectory, "${contentHash}.json")
manifestFile.setText(JsonOutput.prettyPrint(JsonOutput.toJson(manifest)) + '\n', 'UTF-8')
manifests[cardId] = manifest
manifestIds[cardId] = "sha256:${contentHash}".toString()
}
logger.lifecycle(
"generateJpaEvidenceManifests: wrote ${manifests.size()} ${profile} " +
"content-addressed card manifests to ${outputDirectory}")
}
}
gradle.taskGraph.whenReady { graph ->
if (graph.hasTask(generateJpaEvidenceManifests.get())) {
[
tasks.named('test').get(),
project(':app-bootstrap').tasks.named('test').get()
].each { Task testTask ->
testTask.outputs.upToDateWhen { false }
}
}
}
def verifyJpaCandidateEvidence = tasks.register('verifyJpaCandidateEvidence') {
group = 'verification'
description = 'Validates hashes, schema, exact JUnit selectors, no-skip, and prerequisite links without claiming R2.'
dependsOn generateJpaEvidenceManifests
outputs.upToDateWhen { false }
doLast {
Map<String, Object> result = verifyJpaEvidenceDirectory(
jpaEvidenceOutputDirectory.get().asFile,
loadJpaEvidenceRegistry())
List<String> violations = result.violations as List<String>
if (!violations.isEmpty()) {
throw new GradleException(
"verifyJpaCandidateEvidence: ${violations.size()} violation(s):\n " +
violations.toSorted().join('\n '))
}
Map<String, Object> manifests = result.manifests as Map<String, Object>
manifests.each { String cardId, Object rawManifest ->
Map<String, Object> manifest = rawManifest as Map<String, Object>
List<String> missing = manifest.missingEvidence as List<String>
logger.lifecycle(
"${cardId}: ${manifest.attainedReadiness}/${manifest.evidenceGrade}, " +
"${manifest.testResult.executedTestCount} tests, " +
"missing=${missing.isEmpty() ? 'none' : missing.join(',')}")
}
logger.lifecycle(
"verifyJpaCandidateEvidence: OK — ${manifests.size()} manifests are " +
'content-addressed, linked, zero-skip candidate evidence; no R2 claim was made.')
}
}
tasks.register('verifyJpaPrimaryFoundationEvidence') {
group = 'verification'
description = 'Requires complete immutable base-card manifests from a clean, retained CI R2 evidence lane.'
dependsOn generateJpaEvidenceManifests
outputs.upToDateWhen { false }
doLast {
Map<String, Object> result = verifyJpaEvidenceDirectory(
jpaEvidenceOutputDirectory.get().asFile,
loadJpaEvidenceRegistry())
List<String> violations = result.violations as List<String>
Map<String, Object> manifests = result.manifests as Map<String, Object>
Map<String, Object> primary =
manifests['jpa-primary-foundation'] as Map<String, Object>
if ((primary?.profile as String) != 'r2') {
violations << 'jpa-primary-foundation: run with -PjpaEvidenceProfile=r2 in the dedicated CI lane'
}
[
'jpa-observability-lifecycle',
'jpa-security-baseline',
'jpa-flyway-migration',
'jpa-transaction-runtime',
'jpa-aggregate-store',
'jpa-query-model',
'jpa-primary-foundation'
].each { String cardId ->
Map<String, Object> manifest = manifests[cardId] as Map<String, Object>
if (manifest == null) {
violations << "${cardId}: manifest is missing"
} else if (manifest.attainedReadiness != 'R2') {
violations << "${cardId}: attained ${manifest.attainedReadiness}; blockers=" +
"${(manifest.readinessBlockers as List).join(',')}"
}
}
if (!violations.isEmpty()) {
throw new GradleException(
"verifyJpaPrimaryFoundationEvidence: ${violations.size()} violation(s):\n " +
violations.toSorted().join('\n '))
}
logger.lifecycle(
'verifyJpaPrimaryFoundationEvidence: OK — six immutable R2 base manifests and the primary DAG are verified.')
}
}
tasks.named('check') {
dependsOn verifyJpaEvidenceHarnessContract
}