merge: integrate messaging R2 polling producer
# Conflicts: # docs/superpowers/specs/2026-07-28-messaging-production-capability-design.md # src/app-bootstrap/gradle.lockfile # src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ArchitectureViolationFixtureTest.java # src/build.gradle
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import groovy.json.JsonSlurper
|
||||
import groovy.json.JsonOutput
|
||||
import groovy.xml.XmlSlurper
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import org.gradle.api.artifacts.dsl.LockMode
|
||||
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
|
||||
import org.gradle.api.tasks.bundling.AbstractArchiveTask
|
||||
@@ -47,6 +50,115 @@ ext.releaseVersion = releaseVersion
|
||||
ext.sourceRevision = sourceRevision
|
||||
ext.traceableVersion = traceableVersion
|
||||
|
||||
// Messaging first-R2 task names are reserved early, but qualification is deliberately fail-closed.
|
||||
// Follow-up owner tasks replace these skeleton actions only when matching tests write schema-valid,
|
||||
// source/profile-bound, payload-free evidence. Merely placing a manifest on disk cannot pass.
|
||||
Map<String, List<String>> messagingVerificationSkeletons = [
|
||||
'verifyMessagingPollingOutboxR2': [
|
||||
'app-bootstrap/build/messaging-evidence/polling-outbox-r2/manifest.json'
|
||||
],
|
||||
'verifyMessagingKafkaProducerR2': [
|
||||
'app-bootstrap/build/messaging-evidence/real-kafka-postgresql-r2/manifest.json'
|
||||
],
|
||||
'verifyMessagingSecurityR2': [
|
||||
'app-bootstrap/build/messaging-evidence/security-r2/manifest.json',
|
||||
'app-bootstrap/build/messaging-evidence/multi-broker-r2/manifest.json',
|
||||
'app-bootstrap/build/messaging-evidence/rotation-shutdown-r2/manifest.json'
|
||||
],
|
||||
'verifyMessagingReleaseProfile': [
|
||||
'build/messaging-evidence/contracts-schema/manifest.json',
|
||||
'app-bootstrap/build/messaging-evidence/polling-outbox-r2/manifest.json',
|
||||
'app-bootstrap/build/messaging-evidence/cutover-rehearsal/manifest.json',
|
||||
'app-bootstrap/build/messaging-evidence/real-kafka-postgresql-r2/manifest.json',
|
||||
'app-bootstrap/build/messaging-evidence/security-r2/manifest.json',
|
||||
'app-bootstrap/build/messaging-evidence/multi-broker-r2/manifest.json',
|
||||
'app-bootstrap/build/messaging-evidence/rotation-shutdown-r2/manifest.json'
|
||||
],
|
||||
'verifyMessagingTargetBindingPreflight': [
|
||||
'app-bootstrap/build/messaging-evidence/target-binding-preflight/manifest.json'
|
||||
],
|
||||
'verifyMessagingTargetBinding': [
|
||||
'app-bootstrap/build/messaging-evidence/target-binding-attestation/manifest.json'
|
||||
],
|
||||
'verifyMessagingDeploymentCutover': [
|
||||
'app-bootstrap/build/messaging-evidence/target-binding-attestation/manifest.json',
|
||||
'app-bootstrap/build/messaging-evidence/deployment-cutover/manifest.json'
|
||||
],
|
||||
'verifyMessagingCleanupTargetBinding': [
|
||||
'app-bootstrap/build/messaging-evidence/cleanup-target-binding-attestation/manifest.json'
|
||||
],
|
||||
'verifyMessagingFinalR2Profile': [
|
||||
'app-bootstrap/build/messaging-evidence/target-binding-attestation/manifest.json',
|
||||
'app-bootstrap/build/messaging-evidence/cleanup-target-binding-attestation/manifest.json',
|
||||
'build/messaging-evidence/final-r2-profile/manifest.json'
|
||||
]
|
||||
]
|
||||
|
||||
Closure<Void> messagingFailClosedEvidenceGuard = { String taskName, List<String> relativePaths ->
|
||||
List<File> evidenceFiles = relativePaths.collect { rootProject.file(it) }
|
||||
List<String> violations = evidenceFiles.findAll { !it.isFile() }.collect {
|
||||
"missing evidence ${rootProject.relativePath(it)}"
|
||||
}
|
||||
|
||||
String expectedSourceDigest = providers.gradleProperty('messagingSourceDigest').getOrElse('')
|
||||
String expectedProfileHash = providers.gradleProperty('messagingProfileHash').getOrElse('')
|
||||
if (expectedSourceDigest.isBlank()) {
|
||||
violations << 'missing -PmessagingSourceDigest=sha256:<exact-source-digest>'
|
||||
}
|
||||
if (expectedProfileHash.isBlank()) {
|
||||
violations << 'missing -PmessagingProfileHash=sha256:<exact-profile-hash>'
|
||||
}
|
||||
|
||||
evidenceFiles.findAll { it.isFile() }.each { File evidenceFile ->
|
||||
try {
|
||||
def manifest = new JsonSlurper().parse(evidenceFile)
|
||||
if (manifest.sourceDigest != expectedSourceDigest) {
|
||||
violations << "${rootProject.relativePath(evidenceFile)} has wrong source digest"
|
||||
}
|
||||
if (manifest.hashes?.profile != expectedProfileHash) {
|
||||
violations << "${rootProject.relativePath(evidenceFile)} has mismatched profile hash"
|
||||
}
|
||||
if ((manifest.counts?.skipped ?: 0) != 0 || !(manifest.skips instanceof List) ||
|
||||
!manifest.skips.isEmpty()) {
|
||||
violations << "${rootProject.relativePath(evidenceFile)} contains skipped evidence"
|
||||
}
|
||||
if ((manifest.counts?.failed ?: 0) != 0 || !(manifest.failures instanceof List) ||
|
||||
!manifest.failures.isEmpty()) {
|
||||
violations << "${rootProject.relativePath(evidenceFile)} contains failed evidence"
|
||||
}
|
||||
try {
|
||||
Instant generatedAt = Instant.parse(manifest.generatedAt as String)
|
||||
if (generatedAt.isBefore(Instant.now().minus(Duration.ofHours(24))) ||
|
||||
generatedAt.isAfter(Instant.now().plus(Duration.ofMinutes(5)))) {
|
||||
violations << "${rootProject.relativePath(evidenceFile)} is stale or future-dated"
|
||||
}
|
||||
} catch (RuntimeException ignored) {
|
||||
violations << "${rootProject.relativePath(evidenceFile)} has invalid generatedAt"
|
||||
}
|
||||
} catch (RuntimeException ignored) {
|
||||
violations << "${rootProject.relativePath(evidenceFile)} is not valid JSON evidence"
|
||||
}
|
||||
}
|
||||
|
||||
// Task 2 intentionally has no matching qualification Test tasks or complete schema validator.
|
||||
// This unconditional violation prevents hand-written evidence from manufacturing an R2 PASS.
|
||||
violations << 'qualification producer/tests and common-schema validator are not implemented'
|
||||
throw new GradleException(
|
||||
"${taskName}: FAIL_CLOSED — no R2 claim is available:\n ${violations.join('\n ')}")
|
||||
}
|
||||
|
||||
messagingVerificationSkeletons.each { String taskName, List<String> evidencePaths ->
|
||||
tasks.register(taskName) {
|
||||
group = 'verification'
|
||||
description = "Fail-closed Messaging qualification skeleton for ${taskName}."
|
||||
inputs.files(evidencePaths.collect { rootProject.file(it) }).optional()
|
||||
outputs.upToDateWhen { false }
|
||||
doLast {
|
||||
messagingFailClosedEvidenceGuard(taskName, evidencePaths)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Inbound gRPC adapter (adapter:inbound:grpc) — the Spring Boot BOM does NOT manage io.grpc:* or
|
||||
// protobuf versions, and this repo has no version catalog. Pin them here as the single SSOT so the
|
||||
// grpc module (and the future sample grpc feature) import io.grpc:grpc-bom + protobuf-bom as
|
||||
@@ -289,6 +401,394 @@ configure(subprojects.findAll { it.childProjects.isEmpty() }) {
|
||||
}
|
||||
}
|
||||
|
||||
// Task 6 replaces only the contract/schema skeletons with real, no-match-failing Test lanes.
|
||||
// The manifest is payload-free and is rebuilt only after exact source/artifact/profile properties
|
||||
// and every selected Task 3-6 test have passed in the current invocation.
|
||||
def messagingEvidenceResultRoot = layout.buildDirectory.dir('test-results/messaging-evidence')
|
||||
def registerMessagingQualificationTest = {
|
||||
Project owner, String taskName, List<String> patterns, String resultDirectory ->
|
||||
owner.tasks.register(taskName, Test) {
|
||||
group = 'verification'
|
||||
description = 'Runs exact Messaging Task 3-6 qualification tests without broad discovery.'
|
||||
testClassesDirs = owner.sourceSets.test.output.classesDirs
|
||||
classpath = owner.sourceSets.test.runtimeClasspath
|
||||
useJUnitPlatform()
|
||||
filter {
|
||||
patterns.each { includeTestsMatching(it) }
|
||||
failOnNoMatchingTests = true
|
||||
}
|
||||
failOnNoDiscoveredTests = true
|
||||
reports.junitXml.required = true
|
||||
reports.junitXml.outputLocation =
|
||||
messagingEvidenceResultRoot.map { it.dir(resultDirectory) }
|
||||
reports.html.required = false
|
||||
binaryResultsDirectory =
|
||||
layout.buildDirectory.dir("test-results/messaging-evidence-binary/${resultDirectory}")
|
||||
outputs.upToDateWhen { false }
|
||||
jvmArgs '-Duser.timezone=UTC'
|
||||
}
|
||||
}
|
||||
|
||||
def messagingApplicationQualification = registerMessagingQualificationTest(
|
||||
project(':application-core'),
|
||||
'messagingApplicationContractQualificationTest',
|
||||
[
|
||||
'dev.caskeleton.application.messaging.contract.IntegrationEventContractContributionTest',
|
||||
'dev.caskeleton.application.messaging.event.IntegrationEventDraftTest',
|
||||
'dev.caskeleton.application.messaging.event.ValidatedIntegrationEventTest'
|
||||
],
|
||||
'application')
|
||||
def messagingSharedQualification = registerMessagingQualificationTest(
|
||||
project(':shared-contract'),
|
||||
'messagingSharedSchemaQualificationTest',
|
||||
['dev.caskeleton.shared.contract.messaging.MessagingEnvelopeSchemaResourceTest'],
|
||||
'shared')
|
||||
def messagingSampleQualification = registerMessagingQualificationTest(
|
||||
project(':sample-portfolio'),
|
||||
'messagingSampleContractQualificationTest',
|
||||
['dev.caskeleton.sample.portfolio.application.event.WorkLogReservedContractContributionTest'],
|
||||
'sample')
|
||||
def messagingCompiledQualification = registerMessagingQualificationTest(
|
||||
project(':adapter:outbound:messaging'),
|
||||
'messagingCompiledContractsQualificationTest',
|
||||
[
|
||||
'dev.caskeleton.adapter.outbound.messaging.config.MessagingCapabilityCardRegistryTest',
|
||||
'dev.caskeleton.adapter.outbound.messaging.contract.ContractCatalogCompilerTest',
|
||||
'dev.caskeleton.adapter.outbound.messaging.contract.ContractCatalogDigestTest',
|
||||
'dev.caskeleton.adapter.outbound.messaging.destination.DestinationBindingCompilerTest',
|
||||
'dev.caskeleton.adapter.outbound.messaging.destination.PartitionKeyV1Test'
|
||||
],
|
||||
'compiled')
|
||||
def messagingJsonSchemaQualification = registerMessagingQualificationTest(
|
||||
project(':adapter:outbound:messaging'),
|
||||
'messagingJsonSchemaV1QualificationTest',
|
||||
[
|
||||
'dev.caskeleton.adapter.outbound.messaging.envelope.LocalJsonSchemaRegistryTest',
|
||||
'dev.caskeleton.adapter.outbound.messaging.envelope.JsonSchemaIntegrationEventEncoderTest',
|
||||
'dev.caskeleton.adapter.outbound.messaging.envelope.EnvelopeAdversarialCorpusTest',
|
||||
'dev.caskeleton.adapter.outbound.messaging.qualification.MessagingEvidenceManifestSchemaValidatorTest'
|
||||
],
|
||||
'json-schema')
|
||||
|
||||
def messagingEvidenceFile = layout.buildDirectory.file(
|
||||
'messaging-evidence/contracts-schema/manifest.json')
|
||||
def messagingProfileFile = file('config/messaging/profile-compatibility.yaml')
|
||||
def messagingDigestProperty = { String propertyName ->
|
||||
String value = providers.gradleProperty(propertyName).getOrElse('')
|
||||
if (!(value ==~ /sha256:[a-f0-9]{64}/)) {
|
||||
throw new GradleException(
|
||||
"-P${propertyName}=sha256:<64-lowercase-hex> is required for Messaging evidence.")
|
||||
}
|
||||
value
|
||||
}
|
||||
def messagingSha256Bytes = { byte[] bytes ->
|
||||
'sha256:' + java.util.HexFormat.of().formatHex(
|
||||
MessageDigest.getInstance('SHA-256').digest(bytes))
|
||||
}
|
||||
def messagingSha256FileSet = { String domain, List<File> files ->
|
||||
MessageDigest digest = MessageDigest.getInstance('SHA-256')
|
||||
digest.update(domain.getBytes(java.nio.charset.StandardCharsets.UTF_8))
|
||||
digest.update((byte) 0)
|
||||
files.sort { rootProject.relativePath(it) }.each { File input ->
|
||||
if (!input.isFile()) {
|
||||
throw new GradleException(
|
||||
"Messaging evidence input is missing: ${rootProject.relativePath(input)}")
|
||||
}
|
||||
byte[] path = rootProject.relativePath(input)
|
||||
.getBytes(java.nio.charset.StandardCharsets.UTF_8)
|
||||
byte[] content = input.bytes
|
||||
digest.update(java.nio.ByteBuffer.allocate(Integer.BYTES).putInt(path.length).array())
|
||||
digest.update(path)
|
||||
digest.update(java.nio.ByteBuffer.allocate(Integer.BYTES).putInt(content.length).array())
|
||||
digest.update(content)
|
||||
}
|
||||
'sha256:' + java.util.HexFormat.of().formatHex(digest.digest())
|
||||
}
|
||||
|
||||
def prepareMessagingContractEvidence = tasks.register('prepareMessagingContractEvidence') {
|
||||
group = 'verification'
|
||||
outputs.upToDateWhen { false }
|
||||
doLast {
|
||||
File output = messagingEvidenceFile.get().asFile
|
||||
if (output.exists() && !output.delete()) {
|
||||
throw new GradleException("Could not delete stale Messaging evidence ${output}")
|
||||
}
|
||||
messagingDigestProperty('messagingSourceDigest')
|
||||
messagingDigestProperty('messagingArtifactDigest')
|
||||
String suppliedProfile = messagingDigestProperty('messagingProfileHash')
|
||||
String exactProfile = messagingSha256Bytes(messagingProfileFile.bytes)
|
||||
if (suppliedProfile != exactProfile) {
|
||||
throw new GradleException(
|
||||
"messagingProfileHash does not match exact config/messaging/profile-compatibility.yaml bytes.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[
|
||||
messagingApplicationQualification,
|
||||
messagingSharedQualification,
|
||||
messagingSampleQualification,
|
||||
messagingCompiledQualification,
|
||||
messagingJsonSchemaQualification
|
||||
].each {
|
||||
it.configure {
|
||||
dependsOn prepareMessagingContractEvidence
|
||||
}
|
||||
}
|
||||
|
||||
def messagingEvidenceFromXml = { List<String> resultDirectories ->
|
||||
List<Map<String, String>> cases = []
|
||||
resultDirectories.each { String directory ->
|
||||
File resultDirectory = messagingEvidenceResultRoot.get().dir(directory).asFile
|
||||
fileTree(resultDirectory).matching { include 'TEST-*.xml' }.files.sort().each { File xml ->
|
||||
def suite = new XmlSlurper(false, false).parse(xml)
|
||||
suite.testcase.each { testCase ->
|
||||
boolean failed = !testCase.failure.isEmpty() || !testCase.error.isEmpty()
|
||||
boolean skipped = !testCase.skipped.isEmpty()
|
||||
String simpleClass = testCase.@classname.text().tokenize('.').last()
|
||||
String rawId = "${simpleClass}.${testCase.@name.text()}"
|
||||
String scenarioId = rawId
|
||||
.replace('()', '')
|
||||
.replaceAll('[^A-Za-z0-9._:-]', '-')
|
||||
.replaceAll('-+', '-')
|
||||
cases << [id: scenarioId, failed: failed.toString(), skipped: skipped.toString()]
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cases.isEmpty()) {
|
||||
throw new GradleException('Messaging qualification XML contains no discovered test cases.')
|
||||
}
|
||||
List<String> scenarioIds = cases.collect { it.id }.sort()
|
||||
if (scenarioIds.toSet().size() != scenarioIds.size()) {
|
||||
throw new GradleException('Messaging qualification scenario IDs are not unique.')
|
||||
}
|
||||
int failed = cases.count { it.failed == 'true' }
|
||||
int skipped = cases.count { it.skipped == 'true' }
|
||||
[
|
||||
scenarioIds: scenarioIds,
|
||||
counts: [
|
||||
executed: cases.size(),
|
||||
passed: cases.size() - failed - skipped,
|
||||
failed: failed,
|
||||
skipped: skipped
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
def validateMessagingEvidenceStructure = { Map manifest, String expectedProducer ->
|
||||
Set<String> exactRootKeys = [
|
||||
'schemaVersion', 'sourceDigest', 'artifactDigest', 'producerTask', 'scenarioIds',
|
||||
'counts', 'command', 'generatedAt', 'hashes', 'failures', 'skips',
|
||||
'unsupportedClaims'
|
||||
] as Set
|
||||
Set<String> exactCountKeys = ['executed', 'passed', 'failed', 'skipped'] as Set
|
||||
Set<String> exactHashKeys = ['profile', 'catalog', 'schema', 'settings'] as Set
|
||||
List<String> violations = []
|
||||
if (manifest.keySet() != exactRootKeys) {
|
||||
violations << 'root fields do not match the common manifest schema'
|
||||
}
|
||||
if (manifest.schemaVersion != 1 || manifest.producerTask != expectedProducer) {
|
||||
violations << 'schemaVersion or producerTask is wrong'
|
||||
}
|
||||
['sourceDigest', 'artifactDigest'].each { String field ->
|
||||
if (!(manifest[field] instanceof String) ||
|
||||
!(manifest[field] ==~ /sha256:[a-f0-9]{64}/)) {
|
||||
violations << "${field} is not a canonical SHA-256"
|
||||
}
|
||||
}
|
||||
if (!(manifest.scenarioIds instanceof List) || manifest.scenarioIds.isEmpty() ||
|
||||
manifest.scenarioIds.toSet().size() != manifest.scenarioIds.size() ||
|
||||
manifest.scenarioIds.any {
|
||||
!(it instanceof String) ||
|
||||
!(it ==~ /[A-Za-z0-9][A-Za-z0-9._:-]{0,159}/)
|
||||
}) {
|
||||
violations << 'scenarioIds violate the common schema'
|
||||
}
|
||||
if (!(manifest.counts instanceof Map) || manifest.counts.keySet() != exactCountKeys ||
|
||||
!(manifest.counts.executed instanceof Integer) || manifest.counts.executed < 1 ||
|
||||
manifest.counts.values().any { !(it instanceof Integer) || it < 0 } ||
|
||||
manifest.counts.executed !=
|
||||
manifest.counts.passed + manifest.counts.failed + manifest.counts.skipped) {
|
||||
violations << 'counts are invalid or inconsistent'
|
||||
}
|
||||
if (manifest.counts?.failed != 0 || manifest.counts?.skipped != 0 ||
|
||||
manifest.failures != [] || manifest.skips != []) {
|
||||
violations << 'failed or skipped qualification cannot produce PASS evidence'
|
||||
}
|
||||
if (!(manifest.hashes instanceof Map) || manifest.hashes.keySet() != exactHashKeys ||
|
||||
manifest.hashes.values().any {
|
||||
!(it instanceof String) || !(it ==~ /sha256:[a-f0-9]{64}/)
|
||||
}) {
|
||||
violations << 'hashes violate the common schema'
|
||||
}
|
||||
if (!(manifest.command instanceof String) || manifest.command.isBlank() ||
|
||||
manifest.command.length() > 2048) {
|
||||
violations << 'command is missing or unbounded'
|
||||
}
|
||||
try {
|
||||
Instant.parse(manifest.generatedAt as String)
|
||||
} catch (RuntimeException ignored) {
|
||||
violations << 'generatedAt is not UTC date-time evidence'
|
||||
}
|
||||
if (!(manifest.unsupportedClaims instanceof List) ||
|
||||
manifest.unsupportedClaims.toSet().size() != manifest.unsupportedClaims.size() ||
|
||||
manifest.unsupportedClaims.any {
|
||||
!(it instanceof String) ||
|
||||
!(it ==~ /[A-Za-z0-9][A-Za-z0-9._:-]{0,159}/)
|
||||
}) {
|
||||
violations << 'unsupportedClaims violate the common schema'
|
||||
}
|
||||
if (!violations.isEmpty()) {
|
||||
throw new GradleException(
|
||||
"Messaging evidence fails the common schema structural validator:\n " +
|
||||
violations.join('\n '))
|
||||
}
|
||||
}
|
||||
|
||||
def writeMessagingEvidence = {
|
||||
String producerTask, List<String> resultDirectories, List<String> commandTasks ->
|
||||
Map result = messagingEvidenceFromXml(resultDirectories)
|
||||
Map manifest = [
|
||||
schemaVersion: 1,
|
||||
sourceDigest: messagingDigestProperty('messagingSourceDigest'),
|
||||
artifactDigest: messagingDigestProperty('messagingArtifactDigest'),
|
||||
producerTask: producerTask,
|
||||
scenarioIds: result.scenarioIds,
|
||||
counts: result.counts,
|
||||
command: './gradlew ' + commandTasks.join(' ') +
|
||||
' -PmessagingSourceDigest=<sha256> -PmessagingArtifactDigest=<sha256> ' +
|
||||
'-PmessagingProfileHash=<exact-sha256> --console=plain',
|
||||
generatedAt: Instant.now().toString(),
|
||||
hashes: [
|
||||
profile: messagingSha256Bytes(messagingProfileFile.bytes),
|
||||
catalog: messagingSha256FileSet(
|
||||
'ca-skeleton.messaging.evidence.catalog.v1',
|
||||
[file('config/messaging/readiness-cards.yaml')]),
|
||||
schema: messagingSha256FileSet(
|
||||
'ca-skeleton.messaging.evidence.schema-set.v1',
|
||||
[
|
||||
file('shared-contract/src/main/resources/contracts/messaging/envelope/v1.schema.json'),
|
||||
file('sample-portfolio/src/main/resources/contracts/messaging/portfolio.worklog.reserved/v1.schema.json')
|
||||
] + fileTree(
|
||||
'adapter/outbound/messaging/src/main/resources/contracts/messaging/meta/draft-2020-12'
|
||||
).files.toList()),
|
||||
settings: messagingSha256FileSet(
|
||||
'ca-skeleton.messaging.evidence.settings.v1',
|
||||
[
|
||||
file('adapter/outbound/messaging/build.gradle'),
|
||||
file('adapter/outbound/messaging/gradle.lockfile')
|
||||
])
|
||||
],
|
||||
failures: [],
|
||||
skips: [],
|
||||
unsupportedClaims: [
|
||||
'consumer-compatibility-full-suite',
|
||||
'durable-outbox-r2',
|
||||
'kafka-acknowledged-r2',
|
||||
'regex-engine-timeout',
|
||||
'remote-schema-resolution'
|
||||
]
|
||||
]
|
||||
validateMessagingEvidenceStructure(manifest, producerTask)
|
||||
File commonSchema =
|
||||
file('config/messaging/evidence/build-evidence-manifest-v1.schema.json')
|
||||
if (!commonSchema.isFile()) {
|
||||
throw new GradleException('Common Messaging evidence schema is missing.')
|
||||
}
|
||||
File output = messagingEvidenceFile.get().asFile
|
||||
output.parentFile.mkdirs()
|
||||
output.text = JsonOutput.prettyPrint(JsonOutput.toJson(manifest)) + System.lineSeparator()
|
||||
Map reloaded = new JsonSlurper().parse(output) as Map
|
||||
validateMessagingEvidenceStructure(reloaded, producerTask)
|
||||
logger.lifecycle(
|
||||
"${producerTask}: wrote payload-free evidence with ${result.counts.executed} scenarios.")
|
||||
}
|
||||
|
||||
def verifyMessagingJsonSchemaV1 = tasks.register('verifyMessagingJsonSchemaV1') {
|
||||
group = 'verification'
|
||||
description = 'Qualifies the deterministic local Draft 2020-12 envelope candidate.'
|
||||
dependsOn messagingJsonSchemaQualification
|
||||
dependsOn project(':adapter:outbound:messaging').tasks.named('verifyJsonSchemaRuntimeGraph')
|
||||
outputs.file(messagingEvidenceFile)
|
||||
outputs.upToDateWhen { false }
|
||||
doLast {
|
||||
writeMessagingEvidence(
|
||||
'verifyMessagingJsonSchemaV1',
|
||||
['json-schema'],
|
||||
[':adapter:outbound:messaging:messagingJsonSchemaV1QualificationTest',
|
||||
'verifyMessagingJsonSchemaV1'])
|
||||
}
|
||||
}
|
||||
|
||||
def validateMessagingJsonSchemaV1EvidenceManifestSchema =
|
||||
tasks.register('validateMessagingJsonSchemaV1EvidenceManifestSchema', JavaExec) {
|
||||
group = 'verification'
|
||||
description =
|
||||
'Validates the exact generated JSON qualification manifest bytes against the common Draft 2020-12 schema.'
|
||||
dependsOn verifyMessagingJsonSchemaV1
|
||||
classpath =
|
||||
project(':adapter:outbound:messaging').sourceSets.test.runtimeClasspath
|
||||
mainClass =
|
||||
'dev.caskeleton.adapter.outbound.messaging.qualification.MessagingEvidenceManifestSchemaValidator'
|
||||
args file('config/messaging/evidence/build-evidence-manifest-v1.schema.json')
|
||||
.absolutePath,
|
||||
messagingEvidenceFile.get().asFile.absolutePath
|
||||
inputs.file(file('config/messaging/evidence/build-evidence-manifest-v1.schema.json'))
|
||||
inputs.file(messagingEvidenceFile)
|
||||
outputs.upToDateWhen { false }
|
||||
}
|
||||
verifyMessagingJsonSchemaV1.configure {
|
||||
finalizedBy validateMessagingJsonSchemaV1EvidenceManifestSchema
|
||||
}
|
||||
|
||||
def verifyMessagingContracts = tasks.register('verifyMessagingContracts') {
|
||||
group = 'verification'
|
||||
description = 'Qualifies the closed Task 3-6 contract, catalog, binding and schema candidate.'
|
||||
dependsOn validateMessagingJsonSchemaV1EvidenceManifestSchema
|
||||
dependsOn messagingApplicationQualification
|
||||
dependsOn messagingSharedQualification
|
||||
dependsOn messagingSampleQualification
|
||||
dependsOn messagingCompiledQualification
|
||||
dependsOn messagingJsonSchemaQualification
|
||||
dependsOn project(':adapter:outbound:messaging').tasks.named('verifyJsonSchemaRuntimeGraph')
|
||||
outputs.file(messagingEvidenceFile)
|
||||
outputs.upToDateWhen { false }
|
||||
doLast {
|
||||
writeMessagingEvidence(
|
||||
'verifyMessagingContracts',
|
||||
['application', 'shared', 'sample', 'compiled', 'json-schema'],
|
||||
[
|
||||
':application-core:messagingApplicationContractQualificationTest',
|
||||
':shared-contract:messagingSharedSchemaQualificationTest',
|
||||
':sample-portfolio:messagingSampleContractQualificationTest',
|
||||
':adapter:outbound:messaging:messagingCompiledContractsQualificationTest',
|
||||
':adapter:outbound:messaging:messagingJsonSchemaV1QualificationTest',
|
||||
'verifyMessagingContracts'
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
def validateMessagingContractsEvidenceManifestSchema =
|
||||
tasks.register('validateMessagingContractsEvidenceManifestSchema', JavaExec) {
|
||||
group = 'verification'
|
||||
description =
|
||||
'Validates the exact generated combined qualification manifest bytes against the common Draft 2020-12 schema.'
|
||||
dependsOn verifyMessagingContracts
|
||||
classpath =
|
||||
project(':adapter:outbound:messaging').sourceSets.test.runtimeClasspath
|
||||
mainClass =
|
||||
'dev.caskeleton.adapter.outbound.messaging.qualification.MessagingEvidenceManifestSchemaValidator'
|
||||
args file('config/messaging/evidence/build-evidence-manifest-v1.schema.json')
|
||||
.absolutePath,
|
||||
messagingEvidenceFile.get().asFile.absolutePath
|
||||
inputs.file(file('config/messaging/evidence/build-evidence-manifest-v1.schema.json'))
|
||||
inputs.file(messagingEvidenceFile)
|
||||
outputs.upToDateWhen { false }
|
||||
}
|
||||
verifyMessagingContracts.configure {
|
||||
finalizedBy validateMessagingContractsEvidenceManifestSchema
|
||||
}
|
||||
|
||||
// One explicit command regenerates every module's Gradle-default lockfile.
|
||||
tasks.register('resolveAndLockAll') {
|
||||
group = 'build setup'
|
||||
|
||||
Reference in New Issue
Block a user