# build-logic The included build that holds this repository's Gradle conventions. A convention lives here when the same machine code was otherwise copied into more than one place, and the copies could drift apart without any check noticing. ## What is here | Plugin | Owns | | --- | --- | | `ca.architecture-registry` (settings) | project inclusion, directory mapping, and the parsed registry every other reader shares | | `ca.strict-test-lane` | tagged / named / own-source-set Test lanes that cannot pass without executing something | | `ca.strict-qualification` | qualification lanes that cannot pass without executing every named class, re-checked against the JUnit XML | | `ca.evidence` | the JUnit XML reader and the no-skip / required-class rules built on it | | `ca.api-surface` | read-only API surface verification with an explicit, separate update task | | `ca.dependency-policy` | declared absences, checked against the resolved graph rather than against a comment | | `ca.runtime-membership` | the resolved runtime project closure against the registry's memberships | | `ca.platform-module` | what a vendored platform leaf (`messaging:*`, `grpc:*`, `grpc-advanced:*`) is: `java-library` | | `ca.grpc-platform-module` | `ca.platform-module` plus the module-scope `io.grpc:grpc-bom` import, for the leaves that already had it | `ca.testkit-publisher` was here and is not any more: every leaf with a `testkit` source set moved to Gradle's own `java-test-fixtures` (ADR-BUILD-001). Plain classes rather than plugins, because settings and projects load plugins through different mechanisms and more than one caller needs each: | Class | Owns | | --- | --- | | `dev.caskeleton.buildlogic.ModuleRegistry` | reading and validating `config/architecture/modules.json`, including which runtime compositions exist | | `dev.caskeleton.buildlogic.JUnitEvidence` | one JUnit XML reader, with DOCTYPE processing off and no defaulting of absent counts | | `dev.caskeleton.buildlogic.RequiredTestExecution` | the single decision "a test this build names must actually have run", asked by `ca.strict-test-lane` and by `ca.evidence` | | `dev.caskeleton.buildlogic.JavaPublicTypes` | public top-level types, parsed by javac rather than matched with a regular expression | ## What the design named and this build does not have The remediation design's §10.2 listed eight conventions. Two of them were attempted or assessed and deliberately not built, and the reasons belong next to the code rather than in a review thread. ### `ca.java-leaf` — reverted Written, measured, reverted. The full reasoning is in `src/build.gradle` beside the static-analysis block it would have moved. In short: a recorded decision (`feature-static-analysis-quality-contract` D8) already put that baseline in the root `subprojects {}` block; the block is applied once and copied nowhere, so extracting it removes no duplication; and build-logic would have to re-declare the spotless / spotbugs / errorprone / dependency-management coordinates *and their versions*, which creates a drift surface where there was none. A content-level baseline of the resolved analysis configuration — compiler args, encoding, release, Checkstyle tool version and config, SpotBugs effort and report level, for every source set of every project — was captured before the attempt and compared after the revert, because the task graph cannot see a weakened Error Prone flag: the task names are identical either way. The two dumps are identical. ### `ca.optional-adapter` — not warranted Its stated responsibility was "activation metadata and disabled/on composition contract wiring". Neither half is build machine code in this repository: - Activation metadata is one registry, `docs/registries/env-keys.yaml`, verified by one root task, `verifyEnvKeys`. There is no per-leaf copy for a convention to deduplicate. - The off invariant and the on fail-closed contract are ordinary tests over the shared `test` source set, gated by the master switch in `@ConditionalOnProperty` at runtime. They need no source set, no configuration, and no task of their own. A plugin here would have to invent state to hold — an `optionalAdapter { switch = '...' }` block that no build step reads — and a declaration nothing checks is worse than no declaration, because it reads like a guarantee. ## Testing a convention ```bash cd src ./gradlew -p build-logic test --console=plain ``` The lane and registry conventions are covered by Gradle TestKit against real builds rather than by reading the plugin source, because the properties that matter — a lane that discovers nothing fails, a lane never reports up-to-date, a malformed registry is refused before any project is included — are runtime behaviour rather than text in a script. Two of those tests exist because the obvious reading of the Gradle documentation was wrong: `failOnNoDiscoveredTests` does not fire when a tag filter matches nothing, and `failOnNoMatchingTests` does not fire when only *some* of the named tests are missing.