9.8 KiB
Harness-Free Module and Gradle Hygiene Design
- Date: 2026-07-25
- Status: Approved
- Mode: B reconstruction without
.harness - Scope: all 19 Gradle leaves, dependency declarations, test baselines, Mongo scaffolding, runtime-composition documentation, and dependency locks
- Topology SSOT:
src/config/architecture/modules.json
1. Context
The 19-leaf project dependency graph obeys the registered allowed edges, and the three core production source sets are free of Spring, persistence, transport, logging, and metrics imports. The audit nevertheless found a wider declared graph than the source graph, Spring WebMVC test libraries on pure-core test classpaths, Boot 3-era OpenAPI tooling on Spring Boot 4, example-domain code in the production Mongo adapter, and direct MDC access in sample application services.
This design follows the user-approved Mode B reconstruction. It does not recreate or depend on
.harness; settings and verification continue to consume the JSON registry.
2. Goals
- Keep the exact 19 leaves and all allowed project edges in the JSON registry.
- Remove only dependencies proven unnecessary by source/test inspection plus focused compile/test verification.
- Give
domain-core,application-core, andshared-contractJUnit/AssertJ-only test classpaths. - Keep Spring Boot 4.0.0 and replace
springdoc-openapi2.x with the Boot 4-compatible 3.0.0 line. - Remove unused direct Jackson 2 declarations from GraphQL and WebSocket.
- Require the Spring configuration processor exactly in leaves whose main source declares
@ConfigurationProperties. - Remove adapter-local
Example*business concepts frompersistence-mongo; retain only opt-in Mongo infrastructure and typed enablement settings. - Replace sample application-layer MDC reads with an application-owned correlation-context port implemented by the inbound web adapter.
- Remove tracked jqwik runtime state and ignore future
.jqwik-databasefiles. - Describe the default bootstrap as the default runtime composition, not as wiring every optional leaf.
- Regenerate only affected strict dependency locks and finish with the full release gates.
3. Non-goals
- No endpoint, persistence schema, public response, outbox transition, or sample-domain behavior change.
- No version catalog, convention-plugin,
buildSrc, module rename, or registry schema expansion. - No automatic addition of GraphQL, gRPC, WebSocket, Mongo, file server, or object storage to the
default
app-bootstrapruntime. - No stage, commit, amend, or push.
4. Approved dependency decisions
An allowed registry edge is permission, not an obligation to declare it.
| Leaf | Remove after focused proof | Preserve |
|---|---|---|
application-core |
unused domain-core edge |
shared-contract |
inbound:web |
unused domain-core edge |
application/shared and transport dependencies |
inbound:graphql |
application/domain edges, direct Jackson 2, unused processor | shared and GraphQL/web test transport |
inbound:grpc |
application/domain edges, unused annotations/direct protobuf declarations | shared, netty, services, configuration processor |
inbound:websocket |
application/shared edges, direct Jackson 2 | domain, WebSocket, configuration processor |
outbound:support |
domain/application/shared edges | autoconfigure and SLF4J API |
outbound:cache-redis |
domain/application, unused Groovy/Spock | shared/support |
outbound:httpclient |
domain/application | shared/support, actual Groovy/Spock tests |
outbound:identifier |
domain, uuid-creator |
application, actual Groovy/Spock tests |
outbound:messaging |
domain, unused Groovy/Spock | application/shared/support/SLF4J |
outbound:notification |
domain, unused Groovy/Spock | application/shared/support/web/SLF4J |
outbound:persistence-jpa |
domain; explicit Flyway core only if focused compile proves the starter sufficient | application/shared/JPA/PostgreSQL |
outbound:persistence-mongo |
application/shared, Example*, example Testcontainers tests |
Mongo opt-in infrastructure/settings |
outbound:fileserver |
broad Boot starter | application/shared, autoconfigure, SLF4J |
outbound:objectstorage |
broad Boot starter | application/shared/AWS, autoconfigure, SLF4J, vendor IT |
Production composition-root dependencies remain even when bootstrap source does not statically import their types: their purpose is runtime assembly. Duplicate test declarations may be removed only when the focused test classpath continues to compile and execute.
5. Pure-core test and verification policy
domain-core, application-core, and shared-contract receive only JUnit Jupiter, AssertJ, and
the JUnit launcher from the root convention. All other leaves keep the existing Spring test
baseline in this change; family-wide convention plugins are out of scope.
The existing application dependency-purity gate remains. A new registry-driven configuration
processor parity gate applies this Boolean invariant to every leaf and is wired into check:
main source contains one or more exact @ConfigurationProperties( occurrences if and only if the
leaf build.gradle contains exactly one Spring configuration-processor declaration. It must ignore
@ConfigurationPropertiesScan; the number of settings classes is not compared with the number of
processor declarations.
6. Spring Boot 4 compatibility
The web adapter changes
org.springdoc:springdoc-openapi-starter-webmvc-api:2.8.6 to 3.0.0, the first stable
springdoc line released for Spring Boot 4.0.0. The existing sample tests that boot a real server
and call /v3/api-docs are the behavior gate. Snapshot changes are accepted only if they are a
deterministic library-version result and retain the public API contract.
Springdoc 3 otherwise widens ApiError.details from the committed type: object to an
unconstrained OAS 3.1 schema. A web-adapter-owned OpenApiCustomizer must restore the object schema
in the final generated document. Both real-server test applications import that production
configuration. shared-contract remains free of Swagger annotations and dependencies.
GraphQL and WebSocket remove direct com.fasterxml.jackson declarations because neither source
set imports them and Spring Boot 4 owns its JSON stack through the relevant starters.
The web adapter retains the JsonNullable value type, but its 0.2.6 artifact also declares
Jackson 2 transitively while this repository supplies explicit Jackson 3 serializers. Before and
after dependency insight plus focused present/null/undefined serialization tests determine whether
that transitive edge can be excluded. Exclusion is applied only if those tests and the real-server
OpenAPI tests pass; springdoc/Swagger's independently required JSON graph is not removed by
assumption.
7. Mongo production boundary
Delete the adapter-local ExampleRecord, document, mapper, repository, repository adapter, and
their tests. MongoPersistenceConfig remains conditional on
ca-skeleton.persistence-mongo.enabled=true and explicitly imports the Mongo client/data
auto-configurations without owning a fake business repository.
The starter also registers Mongo auto-configuration directly through Boot metadata, independently
of MongoPersistenceConfig. A module-level AutoConfigurationImportFilter, registered through
Boot 4's META-INF/spring.factories discovery path, must exclude the Boot 4 sync/reactive client,
data, repository, health, and metrics Mongo auto-configurations while the enable property is absent
or false. It must allow them unchanged when the property is true; consumers must not need to set
spring.autoconfigure.exclude.
Replacement tests must prove:
- an actual
@EnableAutoConfigurationcontext in default/false mode creates no Mongo infrastructure; - properties bind the enable flag;
- enabled mode can create the infrastructure with a supplied mock
MongoClient, without a real network connection; - production source contains no
Example*type.
The Testcontainers dependencies leave this module when the example repository IT is removed.
8. Correlation context boundary
application-core owns a framework-free CorrelationIdPort whose read result is optional.
adapter:inbound:web implements it from the sanitized request MDC correlation key.
CreateWorkLogUseCase and PosterEventPublisher depend only on the port and preserve the current
fallback to the generated event id when no correlation id exists.
Tests first pin present/blank/absent behavior and prove the sample application packages no longer import SLF4J/MDC. Diagnostic storage remains an adapter concern.
9. Runtime composition and generated state
app-bootstrap keeps its current default runtime modules. Its build description and README must
state that optional leaves require an explicit registry and composition-root dependency change.
Optional adapters remain independently buildable and testable.
The tracked four-byte src/sample-portfolio/.jqwik-database is generated runtime state. Delete it
and add .jqwik-database to src/.gitignore; retain jqwik itself because property tests use it.
10. Verification
Run focused compile/tests before and after each dependency group. Regenerate locks only through
each affected leaf's :leaf-path:resolveAndLockAll --write-locks task, then run:
cd src
./gradlew check --console=plain
./gradlew test --console=plain
./gradlew verifyCleanArchitectureDependencies --console=plain
./gradlew verifyApplicationCoreDependencyPurity --console=plain
./gradlew verifyConfigurationPropertiesProcessor --console=plain
./gradlew verifyDependencyLocks --console=plain
./gradlew verifyPublicPathSnapshot verifyEnvKeys --console=plain
Completion requires fresh review, git diff --check, and an LLM Wiki branch note or an explicit
capture blocker for the mandated exact vault path.