# evidence 250 — messaging-certification-gate-chain
# revision: a24ece9cf797f7ea647e33bf846b115208ed1ba5
# cwd: /shared/codebase/clean-architecture-backend-template/src/messaging
# command: sed -n "36,130p" messaging-kafka/build.gradle; echo; echo ---CI JOB---; sed -n "44,52p" ../../.github/workflows/messaging-certification.yml
# ---- raw output ----
// `CertifiedEvidence` used to be a hand-written list of three scenarios, and every consumer of it —
// the compatibility matrix, the cross-broker release suite, the support matrix document — believed
// it without a container ever starting. The lane below produces that list, and
// `verifyMessagingCertificationEvidence` refuses to pass when the committed manifest claims a
// scenario this run did not prove. Editing the manifest by hand therefore fails the build; running
// the lane rewrites it.
strictTestLanes {
    lane('messagingCertificationTest') {
        tag = 'messaging-certification'
        description = 'Runs the broker fault scenarios against a real Kafka and writes the ' +
                'certification evidence the compatibility matrix reads.'
        customize = { test ->
            // Pinned images. A certification claim names the build it was made against, so a
            // floating tag would make a red run unattributable and a green one unrepeatable.
            // `-PmessagingKafkaImage=` overrides for a one-off run against another version.
            test.systemProperty 'messaging.kafka.image',
                    (project.findProperty('messagingKafkaImage') ?: 'apache/kafka:4.1.0').toString()
            test.systemProperty 'messaging.toxiproxy.image',
                    (project.findProperty('messagingToxiproxyImage')
                            ?: 'ghcr.io/shopify/toxiproxy:2.12.0').toString()
            test.systemProperty 'messaging.certification.manifest',
                    project.layout.buildDirectory
                            .file('messaging-certification/broker-certification-evidence.jsonl')
                            .get().asFile.absolutePath
            // The commit is part of the evidence: "certified" is a claim about one source tree.
            // Read from the environment rather than a `-P` flag so the CI job's command line stays
            // the literal grammar the gate matrix lint accepts.
            test.systemProperty 'messaging.certification.commit',
                    (project.findProperty('certificationCommit')
                            ?: providers.environmentVariable('GITHUB_SHA').getOrElse('local'))
                            .toString()
        }
    }
}

// The certification tag is excluded from `test` because the lane deliberately carries no Docker
// guard. Every other container suite here skips with a stated reason when Docker is absent; a lane
// that skipped would report success for a broker nobody started, which is the whole failure the
// evidence exists to rule out. Keeping it out of `test` is what lets it fail closed without
// breaking a laptop build.
tasks.named('test', Test) {
    useJUnitPlatform {
        excludeTags 'quarantine', 'messaging-certification'
    }
}

tasks.register('verifyMessagingCertificationEvidence') {
    group = 'verification'
    description = 'Fails when the committed broker certification manifest claims a scenario the ' +
            'certification lane did not produce.'
    dependsOn 'messagingCertificationTest'

    def produced = layout.buildDirectory
            .file('messaging-certification/broker-certification-evidence.jsonl')
    def committed = rootProject.file(
            'messaging/messaging-testkit/src/main/resources/messaging/' +
                    'broker-certification-evidence.jsonl')
    inputs.file(produced)
    inputs.file(committed)
    outputs.file(layout.buildDirectory.file('reports/messaging-certification-evidence.txt'))
    // A gate whose result can be served from an earlier run is evidence about that run.
    outputs.upToDateWhen { false }

    doLast {
        // The commit and the observation instant differ on every run by design, so they are not
        // part of the comparison — what has to match is which adapter proved which scenario against
        // which image, and which test produced it.
        Closure<Set<String>> claims = { File file ->
            file.readLines('UTF-8')
                    .findAll { !it.trim().isEmpty() }
                    .collect { line ->
                        line.replaceAll(/,"gitCommit":"[^"]*"/, '')
                                .replaceAll(/,"observedAt":"[^"]*"/, '')
                    }
                    .toSet()
        }
        Set<String> ran = claims(produced.get().asFile)
        Set<String> shipped = claims(committed)

        if (ran != shipped) {
            def unproven = shipped - ran
            def unrecorded = ran - shipped
            throw new GradleException(
                    "the committed certification manifest does not match this run.\n" +
                            "  claimed but not produced: ${unproven.isEmpty() ? 'none' : unproven}\n" +
                            "  produced but not claimed: ${unrecorded.isEmpty() ? 'none' : unrecorded}\n" +
                            "Copy ${produced.get().asFile} over ${committed} — the manifest is a " +
                            "record of a run, not a statement about one.")
        }
        def report = outputs.files.singleFile
        report.parentFile.mkdirs()
        report.text = "scenarios=${ran.size()} manifest=${committed}\n"
    }
}

---CI JOB---
          cache-dependency-path: |
            src/**/*.gradle
            src/**/gradle-wrapper.properties
            src/**/gradle.lockfile
      - name: Certify the Kafka adapter against a real broker
        working-directory: src
        # GITHUB_SHA is read by the lane and written into every evidence line, because "certified"
        # is a claim about one source tree.
        run: ./gradlew :messaging:messaging-kafka:verifyMessagingCertificationEvidence --no-daemon --stacktrace
