<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>eu.exeris</groupId>
    <artifactId>exeris-sdk-root</artifactId>
    <version>0.12.0</version>
    <packaging>pom</packaging>

    <name>Exeris SDK :: Root</name>
    <description>
        Exeris SDK — annotations, source model, and UI kit for Entity-First development.
        Pure API: zero runtime coupling to the Exeris kernel. Apache-2.0.
    </description>
    <url child.project.url.inherit.append.path="false">https://github.com/exeris-systems/exeris-sdk</url>
    <inceptionYear>2026</inceptionYear>

    <organization>
        <name>Exeris Systems</name>
        <url>https://exeris.eu</url>
    </organization>

    <licenses>
        <license>
            <name>Apache License, Version 2.0</name>
            <url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
            <distribution>repo</distribution>
        </license>
    </licenses>

    <developers>
        <developer>
            <id>arkstack-dev</id>
            <name>Arkadiusz Przychocki</name>
            <email>oss@exeris.eu</email>
            <organization>Exeris Systems</organization>
            <organizationUrl>https://exeris.eu</organizationUrl>
        </developer>
    </developers>

    <scm child.scm.connection.inherit.append.path="false"
         child.scm.developerConnection.inherit.append.path="false"
         child.scm.url.inherit.append.path="false">
        <connection>scm:git:git://github.com/exeris-systems/exeris-sdk.git</connection>
        <developerConnection>scm:git:ssh://git@github.com/exeris-systems/exeris-sdk.git</developerConnection>
        <url>https://github.com/exeris-systems/exeris-sdk</url>
        <tag>v0.12.0</tag>
    </scm>

    <issueManagement>
        <system>GitHub Issues</system>
        <url>https://github.com/exeris-systems/exeris-sdk/issues</url>
    </issueManagement>

    <distributionManagement>
        <snapshotRepository>
            <id>central</id>
            <url>https://central.sonatype.com/repository/maven-snapshots</url>
        </snapshotRepository>
        <repository>
            <id>central</id>
            <url>https://central.sonatype.com</url>
        </repository>
    </distributionManagement>

    <modules>
        <module>exeris-sdk-bom</module>
        <module>exeris-sdk-parent</module>

        <!-- Build-time generator, ordered before its consumer: exeris-sdk-annotations
             reaches it through <annotationProcessorPaths>, which Maven does not treat
             as a dependency edge for reactor ordering. Declaration order is what puts
             it first. -->
        <module>exeris-sdk-annotation-catalog</module>
        <module>exeris-sdk-annotations</module>
        <module>exeris-sdk-source-model</module>
        <module>exeris-sdk-source-model-io</module>
        <module>exeris-sdk-composition-spec</module>
        <module>exeris-sdk-composition-lifecycle</module>
        <module>exeris-sdk-composition-runtime</module>
        <module>exeris-sdk-tck</module>
    </modules>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>26</maven.compiler.source>
        <maven.compiler.target>26</maven.compiler.target>
        <!-- JDK 25 LTS — the kernel's GA baseline (ADR-069, following kernel ADR-066).
             Do not raise above the kernel's baseline: these jars sit on a consumer's
             compile classpath, and a higher class-file major locks out the LTS-only
             deployments the kernel deliberately admitted. ClassFileBaselineTest guards
             the emitted major, not this property. -->
        <maven.compiler.release>25</maven.compiler.release>
        <maven.build.timestamp.format>yyyyMMdd-HHmmss</maven.build.timestamp.format>

        <!-- Coverage gate for modules that opt in via jacoco:check execution.
             Annotations module is exempt: @interface declarations have no
             executable instructions, so the metric is not meaningful there. -->
        <jacoco.version>0.8.14</jacoco.version>
        <jacoco.line.coverage.minimum>0.85</jacoco.line.coverage.minimum>
        <jacoco.instruction.coverage.minimum>0.85</jacoco.instruction.coverage.minimum>
        <!--
            A coverage check with no tests behind it measures nothing, and jacoco:check is bound
            to `verify`, so any `install -DskipTests` fails on 0% rather than on whatever the
            build was actually asked to prove.

            This is not hypothetical tidiness. The shared javadoc gate
            (exeris-systems/.github → .github/workflows/javadoc-gate.yml) resolves the reactor with
            `mvn -pl <modules> -am -DskipTests -Dcheckstyle.skip=true install`, and that step —
            unlike the two after it — takes no `extra-maven-args`, so there is no way to pass
            `-Djacoco.skip=true` in from the caller. Without this line the gate dies on
            `exeris-sdk-source-model` with "Coverage checks have not been met", which reads as a
            coverage regression and is nothing of the sort.

            Tying the two properties together says the intended thing directly: skipping the tests
            skips the gate that grades them. `mvn verify` and `mvn install` are unaffected, and the
            85% BUNDLE gate keeps its teeth on every build that runs tests at all.
        -->
        <skipTests>false</skipTests>
        <jacoco.skip>${skipTests}</jacoco.skip>
        <maven.enforcer.version>3.5.0</maven.enforcer.version>

        <!-- Semver gate (1.0.0 GA prerequisite): japicmp, opt-in through the `semver` profile at
             the foot of this file. Configured before the freeze on purpose: it has to encode the
             record-growth stance in MIGRATION-0.x-to-1.0.md §3, and a gate written after the
             freeze would be written against whatever the code already did.

             Opt-in rather than bound to every build: the gate compares each module with the last
             released version, and that version has to be resolvable. The baseline named below,
             0.11.0, is not on Maven Central, so a gate bound to `verify` would fail a fresh-clone
             `mvn install` on artifact resolution. The default build runs the baseline-free guards
             instead (AnnotationSurfaceContractTest, RecordComponentOrderTest,
             RecordConstructorLedgerTest), and a maintainer runs `-Psemver` before a release.

             Inside the profile an absent baseline fails the build, loudly
             (ignoreMissingOldVersion=false): whoever turns the gate on has asked for a
             comparison, and "no baseline found" must not read as "no breaks found".

             It becomes binding in CI once the baseline is a version Central serves: move
             japicmp.baseline.version to it and add -Psemver to build.yml and release.yml. That
             one edit is what starts 1.x binary enforcement; the ROADMAP's 1.0.0 GA semver item
             names the release. -->
        <japicmp.version>0.23.1</japicmp.version>
        <japicmp.baseline.version>0.11.0</japicmp.baseline.version>
        <!-- Per-module opt-out, for use inside the `semver` profile only: the profile binds the
             gate reactor-wide, and a module outside it sets this to true in its own pom
             (exeris-sdk-annotations, which is @Retention(SOURCE) and gated by
             AnnotationSurfaceContractTest instead; exeris-sdk-annotation-catalog, which is not
             published and has no baseline). Nobody needs to pass it on the command line. -->
        <japicmp.skip>false</japicmp.skip>
    </properties>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.14.1</version>
                    <configuration>
                        <release>${maven.compiler.release}</release>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>3.2.5</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-javadoc-plugin</artifactId>
                    <version>3.11.2</version>
                    <configuration>
                        <!--
                            The three OpenJDK contract tags (JDK-8008632), registered here so that
                            javadoc-conventions.md rule 6 can be followed at all.

                            Registration is a precondition, not a claim about the code: across this
                            reactor there is currently no @apiNote, no @implSpec and no @implNote.
                            An unregistered custom tag is itself a doclint error, and every module
                            that matters here builds with failOnWarnings=true — so the first comment
                            to adopt the vocabulary would break the build that is supposed to be
                            enforcing it. Registering them makes writing the tags possible; writing
                            them is review-level work no gate measures.

                            In pluginManagement rather than in each module: the seven gated poms
                            configure this plugin themselves, and their <configuration> merges with
                            this one instead of replacing it, so the vocabulary is declared once.
                        -->
                        <tags>
                            <tag><name>apiNote</name><placement>a</placement><head>API Note:</head></tag>
                            <tag><name>implSpec</name><placement>a</placement><head>Implementation Requirements:</head></tag>
                            <tag><name>implNote</name><placement>a</placement><head>Implementation Note:</head></tag>
                        </tags>
                    </configuration>
                </plugin>
                <!--
                    Checkstyle is never bound to a lifecycle phase here; it exists for one caller,
                    the shared Javadoc gate (exeris-systems/.github → javadoc-gate.yml), which
                    invokes `checkstyle:check` as a CLI goal and points it at the organisation
                    ruleset with -Dcheckstyle.config.location. This block exists only to pin the
                    version, so the gate cannot silently change what it enforces when a new plugin
                    release lands.

                    Deliberately absent: <configLocation>. It is the plugin's documented user
                    property, but a literal <configLocation> in a pom overrides the command line
                    SILENTLY — a bogus path passed with -D does not even fail, it is just ignored.
                    exeris-kernel hit exactly this: the shared gate ran the repository's own ruleset
                    instead of the organisation one and reported clean. If a repository-local
                    ruleset is ever wanted here, express it as a ${checkstyle.config.location}
                    property, never as a literal, so the caller can still win.
                -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-checkstyle-plugin</artifactId>
                    <version>3.6.0</version>
                    <!--
                        The Checkstyle ENGINE is pinned separately from the plugin, and this is the
                        line that makes the gate able to read the code it is gating.

                        maven-checkstyle-plugin 3.6.0 ships Checkstyle 9.3 by default — released in
                        early 2022, months before JDK 18 introduced `{@snippet}`. Its Javadoc grammar
                        has never seen the tag, so every `{@snippet}` block produces

                            Javadoc comment at column 0 has parse error.
                            Details: mismatched input ' *' expecting JAVADOC_INLINE_TAG_END

                        On exeris-sdk-annotations, 9.3 reports 77 such errors and 13.8.0 none, with
                        every other finding identical. The
                        standard mandates `{@snippet}` and the default engine cannot parse it, so on
                        9.3 the gate punishes compliance — and the errors name MissingDeprecated,
                        a check that has nothing to do with any of it.

                        Pinning it also stops the engine drifting under a plugin bump, which for a
                        gate matters more than being current: what it enforces should change when
                        someone decides to change it.
                    -->
                    <dependencies>
                        <dependency>
                            <groupId>com.puppycrawl.tools</groupId>
                            <artifactId>checkstyle</artifactId>
                            <version>13.8.0</version>
                        </dependency>
                    </dependencies>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-source-plugin</artifactId>
                    <version>3.3.1</version>
                </plugin>
                <plugin>
                    <groupId>org.jacoco</groupId>
                    <artifactId>jacoco-maven-plugin</artifactId>
                    <version>${jacoco.version}</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-enforcer-plugin</artifactId>
                    <version>${maven.enforcer.version}</version>
                </plugin>
                <!-- Version only. Everything else about the semver gate — its configuration and
                     its bindings — lives in the `semver` profile, so that nothing in the default
                     build runs it. -->
                <plugin>
                    <groupId>com.github.siom79.japicmp</groupId>
                    <artifactId>japicmp-maven-plugin</artifactId>
                    <version>${japicmp.version}</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>

    <!--
        Everything a Maven Central release needs, and NOTHING the ordinary build touches.
        Activated only by `-P release`.

        The isolation is not tidiness. central-publishing-maven-plugin declares
        <extensions>true</extensions>, which makes it TAKE OVER the `deploy` phase. Declared
        outside this profile it would own every `mvn deploy` this reactor runs, and the takeover
        is silent — the symptom is an artifact not arriving where <distributionManagement> says
        it goes. Profile-scoped, the two channels stay separate by construction.

        Ported from exeris-kernel/pom.xml's `release` profile, which paid for the GPG comment
        below in a dry run. Two things are deliberately NOT ported:

        - maven-source-plugin / maven-javadoc-plugin. The kernel attaches both here because its
          modules do not. Every publishable module in this reactor already attaches them in its
          own pom, and this repo's javadoc build is strict — the annotations module has no
          dependencies, so an unresolved {@link} fails it. The kernel's block sets doclint=none
          and failOnError=false to get past ~160 findings it carries; importing it would switch
          off a gate this repo currently passes clean.

        - The cyclonedx SBOM. The kernel ships one, this reactor does not produce one, and
          adding it is a change of its own, not a rider on another. The trap to read before
          adding it is the kernel's own
          `skipNotDeployed` comment: <extensions>true</extensions> sets maven.deploy.skip, the
          SBOM goal skips on that by default, and a release build therefore produces none while
          the ordinary CI SBOM gate stays green.

        Publication goes through the Central Portal only. The OSSRH flow — nexus-staging-maven-plugin
        and the oss.sonatype.org URLs it needs — is not available and is not used here.
    -->
    <profiles>
        <profile>
            <id>release</id>
            <build>
                <plugins>
                    <!--
                        Central verifies a detached GPG signature on every file it accepts — jar,
                        sources, javadoc and pom alike.

                        No `gpgArguments`, and the absence is the point. A loopback pinentry
                        argument is dead in both directions: with a passphrase available the
                        plugin already emits loopback pinentry and a passphrase file descriptor of
                        its own accord, and without one it appends its own pinentry mode of `error`
                        AFTER the supplied argument, which gpg honours as the last occurrence.
                        Configuration that is redundant when it works and ignored when it does not
                        is worse than none, because it reads as the thing making signing work.

                        What carries the passphrase is the environment variable named by
                        `passphraseEnvName`, and that name is chosen by actions/setup-java's
                        `gpg-passphrase` input, not here. The two have to agree; when they do not,
                        signing fails with "gpg: signing failed: No pinentry" — a message about
                        the agent that says nothing about a name mismatch.
                    -->
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-gpg-plugin</artifactId>
                        <version>3.2.8</version>
                        <executions>
                            <execution>
                                <id>sign-artifacts</id>
                                <phase>verify</phase>
                                <goals>
                                    <goal>sign</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>

                    <!--
                        autoPublish=false is not a placeholder to flip later without thinking. A
                        deployment sitting in the portal can be DROPPED; a published one cannot be
                        touched, ever. The first release goes through manual review of the portal's
                        validation results, and waitUntil=validated makes the build block until
                        there is something to review rather than exiting green on an upload.

                        <excludeArtifacts> is required. exeris-sdk-annotation-catalog is a
                        build-time generator nobody resolves and must not reach Central; it holds
                        itself back from `deploy` with <maven.deploy.skip>true</maven.deploy.skip>
                        in its own pom, but maven.deploy.skip governs maven-deploy-plugin, and this
                        plugin REPLACES maven-deploy-plugin and does not read that property.
                        Without the exclusion it stages the module's jar, pom and both signatures
                        into `target/central-deferred` — the directory whose contents become the
                        upload.

                        Uploaded, that is a coordinate with no sources jar and no javadoc jar,
                        which Central rejects per artifact — so one build-time module would fail
                        the validation of every other module's release.

                        The two settings are therefore not duplicates: they address different
                        plugins, and neither implies the other. Because that is exactly the kind of
                        pair that drifts, the release-readiness step in release.yml asserts the two
                        lists agree rather than trusting them to.
                    -->
                    <plugin>
                        <groupId>org.sonatype.central</groupId>
                        <artifactId>central-publishing-maven-plugin</artifactId>
                        <version>0.11.0</version>
                        <extensions>true</extensions>
                        <configuration>
                            <publishingServerId>central</publishingServerId>
                            <autoPublish>false</autoPublish>
                            <waitUntil>validated</waitUntil>
                            <excludeArtifacts>
                                <artifact>exeris-sdk-annotation-catalog</artifact>
                            </excludeArtifacts>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>

        <!--
            The semver gate, opt-in: `mvn -Psemver verify`. Maintainers run it before a release;
            the default build does not, and does not need a baseline. See the japicmp comment in
            <properties> for why, and for the edit that makes it binding in CI.

            Strict by default, and strict for the AST. Two bindings, both here so that one place
            answers "does japicmp run": `semver-gate`, the strict comparison every jar module gets,
            and `semver-gate-record-growth`, skipped unless a module switches it on. Two modules
            do, for one named record each (ApplyResult in -io, CapManifest in composition-spec),
            through a `semver` profile of their own that configures these two executions and binds
            nothing: the strict run excludes the record and the relaxed run includes it, with
            CONSTRUCTOR_REMOVED accepted as a MINOR change. japicmp 0.23.1's
            overrideCompatibilityChangeParameter carries no class pattern, which is why the scoping
            is two executions rather than one override.

            exeris-sdk-source-model takes no relaxation: a record there that grows keeps its
            previous arity as a delegating constructor, so growth removes nothing (see that
            module's pom). The two
            record-free composition modules have exception classes whose constructor overloads are
            real API, so a blanket relaxation would retire a real safety net for nothing.
            Pom-packaging modules are skipped by the plugin itself (skipPomModules defaults to
            true); exeris-sdk-annotations and exeris-sdk-annotation-catalog set japicmp.skip.
        -->
        <profile>
            <id>semver</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>com.github.siom79.japicmp</groupId>
                        <artifactId>japicmp-maven-plugin</artifactId>
                        <configuration>
                            <skip>${japicmp.skip}</skip>
                            <oldVersion>
                                <dependency>
                                    <groupId>${project.groupId}</groupId>
                                    <artifactId>${project.artifactId}</artifactId>
                                    <version>${japicmp.baseline.version}</version>
                                    <type>jar</type>
                                </dependency>
                            </oldVersion>
                            <parameter>
                                <!-- Absent baseline must not read as "no breaks found". It reads
                                     as "not checked", loudly. -->
                                <ignoreMissingOldVersion>false</ignoreMissingOldVersion>
                                <onlyModified>true</onlyModified>
                                <accessModifier>public</accessModifier>
                                <breakBuildOnBinaryIncompatibleModifications>true
                                </breakBuildOnBinaryIncompatibleModifications>
                                <breakBuildOnSourceIncompatibleModifications>true
                                </breakBuildOnSourceIncompatibleModifications>
                            </parameter>
                        </configuration>
                        <executions>
                            <execution>
                                <id>semver-gate</id>
                                <phase>verify</phase>
                                <goals><goal>cmp</goal></goals>
                            </execution>
                            <execution>
                                <id>semver-gate-record-growth</id>
                                <phase>verify</phase>
                                <goals><goal>cmp</goal></goals>
                                <configuration>
                                    <skip>true</skip>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
</project>
